Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split newton stabilisation as options and add a failsafe #2006

Merged

Conversation

MFraters
Copy link
Member

This is something which I have been working on and with for a while now, and since #1996 is (hopefully) nearly merged, it seems me like a good time to discuss this now. This pull request actually add two features at the same time, because they used to be intertwined, but I untangled them just before this pull request. If you want I can make two pull requests from this one.

The first feature is that it will now be possible to chose whether to use the stabilisation or not (and which of the two) for both the preconditioner and the A block.

My tests with the Spiegelman benchmark (I only tested with preconditioner being SPD) seem to indicate that if the solver doesn't fail, the convergence rate is by far the best if you only symmetrise the the A block (symmetric), then much closer together SPD, none, PD. But both the symmetric and the none fail when problems get tougher. This is why I introduce the second feature.

The second feature is that when the solver doesn't converge, we try again, but set everything to SPD. This way I can get the best of both world (being symmetric and SPD).

If you all agree with this approach, then will actually clean up the code, add more documentation and for example change the strings of "SPD" and "none" into enums.

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch from 8ad1385 to aad87cf Compare November 23, 2017 12:27
@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch from e87ec4a to 9b68a88 Compare January 14, 2018 16:00
Copy link
Member

@gassmoeller gassmoeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle I think this is a nice approach, and we should pursue it. I am a bit hesitant about all those Newton parameters ending up in the main Parameters class though. I would be very happy if those could be moved into the NewtonHandler class like for the melt settings. Let me know when I should take another look (I realize this is something that could take a bit of time).

@@ -308,6 +308,9 @@ namespace aspect

double nonlinear_tolerance;
double nonlinear_switch_tolerance;
std::string use_Newton_stabilisation_preconditioner;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it is time to give the Newton handler its own declare_parameters and parse_parameters functions. This way we do not clutter the main parameters with things that are specific to the Newton handler. The melt handler already does this, and probably you can copy the same approach (see parameters.cc:1728, core.cc:544, melt.cc:1367). Also as you mentioned these can be enum

@@ -115,6 +126,7 @@ namespace aspect
* explanation of the purpose of this factor.
*/
double newton_derivative_scaling_factor;
std::pair<std::string,std::string> Newton_stabilisation;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this as two separate variables and document what they do. You can return them as a pair in get_Newton_stabilization, but set them as two separate input arguments in set_Newton_stabilization

* Gets the Newton derivative scaling factor used for scaling the
* derivative part of the Newton Stokes solver in the assembly.
*/
std::pair<std::string,std::string> get_Newton_stabilisation() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stabilization please. Otherwise we end up in an American vs. British English war 😄.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading through the rest of the PR, I think it would be better to split both functions into separate functions that could be named set_preconditioner_stabilization and set_velocity_block_stabilization respectively (and the same for the get_... functions. This is more intuitive than returning or setting a pair of parameters. Otherwise it would get real complicated if somebody added a third stabilization option.

@@ -308,6 +308,9 @@ namespace aspect

double nonlinear_tolerance;
double nonlinear_switch_tolerance;
std::string use_Newton_stabilisation_preconditioner;
std::string use_Newton_stabilisation_A_block;
bool use_Newton_failsafe;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide this is a good way, we might even omit this variable and always fall back to the failsafe.

@@ -96,6 +96,7 @@ namespace aspect

const double JxW = scratch.finite_element_values.JxW(q);


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this line?

@bangerth
Copy link
Contributor

bangerth commented Feb 5, 2018

@MFraters -- can you address @gassmoeller 's comments first? Once you're done with those, let me know and I'll take another look.

I'd say make this a priority today, if you can!

@MFraters
Copy link
Member Author

MFraters commented Feb 5, 2018

I addressed all comments except the enum one. If I make it an enum, is there an easy way to output the enum name to the screen without a bunch of if statements?

@MFraters MFraters changed the title WIP: Split newton stabilisation as options and add a failsafe Split newton stabilisation as options and add a failsafe Feb 6, 2018
Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple comments on the header file. The rest to come later today.

/**
* Set the stabilization type used in the preconditioner.
*/
void set_preconditioner_stabilization(const std::string preconditioner_stabilization);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean by using these strings. I think it would indeed be cleaner if you defined enums here and in similar places that these functions could take/return

/**
* Sets the stabilization type used in the velocity block.
*/
void set_velocity_block_stabilization(const std::string velocity_block_stabilization);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

/**
* Get whether to use the Newton failsafe
*/
bool get_use_Newton_failsafe();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add another sentence to the documentation that explains what the "failsafe" is?

Also end the sentence with a period.


/**
* Get the nonlinear tolerance at which to switch the
* nonlinear solver from defect corrected Piccard to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> defect correction
-> Picard (one 'c')

double get_nonlinear_switch_tolerance();

/**
* Get the maximum amount of pre-Newton nonlinear iterations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amount -> number
(maybe also in other places; "amount" is used for uncountable things, such as milk, flour, love; "number" is used for countable things, such as iterations, words per doc string, software bugs, etc.)

double newton_derivative_scaling_factor;
double newton_derivative_scaling_factor;
std::string preconditioner_stabilization;
std::string velocity_block_stabilization;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two variables would simply become enums then, possibly of different enum types. Of course you'd have to have a way to translate from a string to an enum and possibly back, but that should be easy to write in the parse_parameters() function.

The advantage is that enums have only finitely many possible values, and so you can easily cover them in a switch statement without much trouble. For std::string objects, there are infinitely many possibilities that, at least in principle, you'd have to check at one point or other. You also can't use them in a switch statement.

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch 2 times, most recently from cfec7ed to 4e2d658 Compare February 7, 2018 18:44
@@ -61,6 +61,16 @@ namespace aspect
* A Class which can declare and parse parameters and creates
* material model outputs for the Newton solver.
*/

enum class PreconditionerStabilization
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above this enum is still about the NewtonHandler class.

I'd say move these enums into the NewtonHandler class. Also add documentation for both.


enum class PreconditionerStabilization
{
SPD, PD, symmetric, none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is an easier way: declare it like this:

  none = 0,
  symmetric = 1;
  PD = 2;
  SPD = symmetric | PD;

You can then in the appropriate place ask whether a particular bit is set.

Same for the enum below.

};
enum class VelocityBlockStabilization
{
SPD, PD, symmetric, none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, since the two are the same, could you not use the same enum for both purposes, and simply have two variables of this enum type?

@@ -646,6 +646,7 @@ namespace aspect
{
assemble_newton_stokes_system = true;
newton_handler->initialize_simulator(*this);
newton_handler->parse_parameters(prm);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you investigate whether this whole if block could be moved to the same location where we currently initialize the melt handler and the free surface handler (around line 530)?

template <int dim>
std::string
NewtonHandler<dim>::
get_velocity_block_stabilization_string(VelocityBlockStabilization velocity_block_stabilization)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this argument const. same in the declaration of the function if it's not yet there. same also for the previous function.

"matrix created for the preconditioning is not necessarily Symmetric Positive Definite. "
"This is problematic (see Fraters et al, in prep). When none is chosen, the perconitioner "
"is not stabilized. The Symmetric parameters symmetrizes the matrix, and PD makes the matrix "
"Positive Definite. SPD is the full stabilization, where the matrix is garanteed Symmetric "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> guaranteed

"Positive Definite.");
prm.declare_entry ("Stabilization velocity block", "SPD",
Patterns::Selection ("SPD|PD|symmetric|none"),
"This parameters allows for the stabilisation of the velocity block. By default, the "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the same issues here. You may also just reference the preconditioner parameter (or the other way around) and just say something like "Stabilization for the velocity block of the system matrix used in the Newton method. Possible values for this parameter are the same as for ..."

"Positive Definite.");
prm.declare_entry ("Use Newton failsafe", "false",
Patterns::Bool (),
"Switches on SPD stabilization when solver fails.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this in a couple more sentences? I.e., what solver needs to fail, and maybe reference what other parameter values are then changed? And for how long -- all future time steps, or just the current time step?

pcout << " The linear solver tolerance is set to " << parameters.linear_stokes_solver_tolerance << std::endl;
const std::string preconditioner_stabilization = newton_handler->get_preconditioner_stabilization_string(newton_handler->get_preconditioner_stabilization());
const std::string velocity_block_stabilization = newton_handler->get_velocity_block_stabilization_string(newton_handler->get_velocity_block_stabilization());
pcout << " The linear solver tolerance is set to " << parameters.linear_stokes_solver_tolerance << ". Stabilisation Preconditioner is " << preconditioner_stabilization << " and A block is " << velocity_block_stabilization << "." << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> stabilization (with a z)

I'd also show the value for the matrix first, before the value for the preconditioner

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think that that order is more logical? In my mind the preconditioner comes first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know -- I think as preconditioners as an ancillary tool to solving the linear system.

I don't feel strongly about the issue if you do :-)

}
catch (...)
{
computing_timer.exit_section();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave a comment here of the form

// start the solve over again and try with a stabilized version

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About line 602: you are exiting a timer block here, but what timer block does the remainder of this block correspond to? Don't you have to start a timer block again somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this exit solver.cc:525 computing_timer.enter_section (" Solve Stokes system"); (at the beginning of solve_stokes(). When the solver fails the computing_timer.exit_section(); on line 921 is never executed, so I do it here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand this.

You have code like this:

  timer.enter_section (...);
  try {
    ...do something...
  } catch (...) {
    timer.leave_section();
    ...code now not associated with any timer section...
  }

  ...code that is now under the timer section again, but only if the try
     succeeded; if the try failed, this whole code block is not timed...

(I can't compare with the current code any more, because the line numbers you cite don't seem to match any more.)

Or are you saying that the solve_stokes() function enters a section at the top but the leaves through a throw statement with leaving the section? That would be a bug in that function -- it needs to leave the timer section before throwing the exception. If you don't right away see how to do this, show me the place tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or are you saying that the solve_stokes() function enters a section at the top but the leaves through a throw statement with leaving the section? That would be a bug in that function -- it needs to leave the timer section before throwing the exception. If you don't right away see how to do this, show me the place tomorrow.

Yes. I opened a separate pull request for this #2084 and removed it from this one.

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch 2 times, most recently from 1b83961 to b1b4d6a Compare February 8, 2018 03:01
Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are the only comments for the file I haven't looked through in any of the other reviews you've already gotten.

// todo: make this 0.9 into a global input parameter
double alpha = Utilities::compute_spd_factor<dim>(eta, strain_rate, viscosity_derivative_wrt_strain_rate, 0.9);
const double alpha = preconditioner_stabilization == NewtonHandler<dim>::NewtonStabilization::SPD || preconditioner_stabilization == NewtonHandler<dim>::NewtonStabilization::PD ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break long lines.

If you do the trick with the bit masks for the enum, you can just write this as

  const double alpha = (preconditioner_stabilization | NewtonHandler<dim>::NewtonStabilization::SPD != 0 ?
                                      ...);

:
1;

if (preconditioner_stabilization == NewtonHandler<dim>::NewtonStabilization::SPD || preconditioner_stabilization == NewtonHandler<dim>::NewtonStabilization::symmetric)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same in all other places where you have to make this sort of comparison

i.e., here you'll have to test preconditioner_stabilization | ...::symmetric

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch 3 times, most recently from 037c654 to 7037fad Compare February 8, 2018 19:38
* only symmetrized, and PD represents that we do the same as
* what we do for SPD, but without the symmetrization.
*/
enum NewtonStabilization
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had change this form enum class NewtonStabilization to enum NewtonStabilization` to make sure I could use the operator|. The downside of this is that is isn't strongly typed anymore. An other option I tried was to add a below this the function:

inline NewtonStabilization operator|(NewtonStabilization a, NewtonStabilization b)
{return static_cast<NewtonStabilization>(static_cast<int>(a) | static_cast<int>(b));}

but then I got the error:

error: ‘aspect::NewtonHandler<dim>::NewtonStabilization aspect::NewtonHandler<dim>::operator|(aspect::NewtonHandler<dim>::NewtonStabilization, aspect::NewtonHandler<dim>::NewtonStabilization)’ must take exactly one argument
       inline NewtonStabilization operator|(NewtonStabilization a, NewtonStabilization b)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't make this function a member of the surrounding NewtonHandler class. It needs to be a function at namespace scope. Just declare it below the end of the NewtonHandler class as

inline
typename NewtonHandler<dim>::NewtonStabilization 
operator|(typename NewtonHandler<dim>::NewtonStabilization a, 
               typename NewtonHandler<dim>::NewtonStabilization b)
{
  return static_cast<typename NewtonHandler<dim>::NewtonStabilization>(
    static_cast<int>(a) | static_cast<int>(b));
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this somehow doesn't work. It doesn't seem to recognise that the inline opertor is pressent, but it compiles newton.h fine. So when I put:

  template <int dim>
  inline
  typename NewtonHandler<dim>::NewtonStabilization
  operator|(typename NewtonHandler<dim>::NewtonStabilization a,
                 typename NewtonHandler<dim>::NewtonStabilization b)
  {
    return static_cast<typename NewtonHandler<dim>::NewtonStabilization>(
      static_cast<int>(a) | static_cast<int>(b));
  }

I get as error:

error: no match for ‘operator|’ (operand types are ‘aspect::NewtonHandler<2>::NewtonStabilization’ and ‘aspect::NewtonHandler<2>::NewtonStabilization’)
               const double alpha = preconditioner_stabilization | NewtonHandler<dim>::NewtonStabilization::PD ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I have tried to add NewtonHandler<dim>::NewtonStabilization operator|(typename NewtonHandler<dim>::NewtonStabilization a, typename NewtonHandler<dim>::NewtonStabilization b); \ to the bottom of newton.cc, but that doesn't help. I also tried to just explicity instanciate them:

  typename NewtonHandler<2>::NewtonStabilization
  operator|(typename NewtonHandler<2>::NewtonStabilization a,
                 typename NewtonHandler<2>::NewtonStabilization b)
  {
    return static_cast<typename NewtonHandler<2>::NewtonStabilization>(
      static_cast<int>(a) | static_cast<int>(b));
  }

  typename NewtonHandler<3>::NewtonStabilization
  operator|(typename NewtonHandler<3>::NewtonStabilization a,
                 typename NewtonHandler<3>::NewtonStabilization b)
  {
    return static_cast<typename NewtonHandler<3>::NewtonStabilization>(
      static_cast<int>(a) | static_cast<int>(b));
  }

This works, but then I will have to specifically convert it into an int or bool again because it complains:

error: could not convert ‘aspect::operator|(velocity_block_stabilization, (aspect::NewtonHandler<2>::NewtonStabilization)1)’ from ‘aspect::NewtonHandler<2>::NewtonStabilization’ to ‘bool’

So if we really want this approach, we would also need to implement a way to covert this class to a bool.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand why it doesn't work.

Providing two specializations is ok with me. If you don't like this approach: What happens if you put the operator back into the NewtonHandler class as you had it initially, but prefix the whole declaration with the friend keyword? I.e.,

friend
NewtonStabilization 
operator| (const NewtonStabilization a, 
                const NewtonStabilization b)
{
  return static_cast<NewtonStabilization>(
    static_cast<int>(a) | static_cast<int>(b));
}

(But if you like the explicit specialization you show, then no need to try this approach.)

As for the conversion to bool: Just do the comparison via

  if ((value | ...::SPD) != ...::none)

...::none has numerical value zero, but has type equal to the enum, so you can compare with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I got it working now with the class enum. I do not have a strong preference for either way, but I guess it is nicer to not have explicit specializations.

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch from 7037fad to 0781284 Compare February 9, 2018 04:44
Copy link
Contributor

@bangerth bangerth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost good. Please fix the two issues marked up and merge yourself!

Thanks for sticking with me through all of these changes!

/**
* This enum describes the type of stabilization is used
* for the Newton solver. None represents no stabilization,
* SPD represent that the resulting matrix is make Symmetric
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make -> made

* only symmetrized, and PD represents that we do the same as
* what we do for SPD, but without the symmetrization.
*/
enum class NewtonStabilization
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I blanked -- enum class is only C++11, but we can't presume that yet for ASPECT. Can you just make it enum? I think everything should continue to work anyway without further modifications.

@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch from 0781284 to 3d62c9e Compare February 9, 2018 15:16
@MFraters MFraters force-pushed the add_Newton_stabilisation_as_failsafe branch from 3d62c9e to 8cc6994 Compare February 9, 2018 15:25
@MFraters MFraters merged commit 4e0a02a into geodynamics:master Feb 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants