Skip to content

Commit 679066c

Browse files
committed
Refactor the Newton line search.
1 parent 888877c commit 679066c

File tree

3 files changed

+162
-115
lines changed

3 files changed

+162
-115
lines changed

include/aspect/simulator.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,26 @@ namespace aspect
13371337
void interpolate_onto_velocity_system(const TensorFunction<1,dim> &func,
13381338
LinearAlgebra::Vector &vec) const;
13391339

1340+
/**
1341+
* Perform a Newton line search to determine the optimal step length
1342+
* along the search direction. After the update, the current_linearization_point
1343+
* is set to the old current_linearlization_point plus a suitable update.
1344+
*
1345+
* @param dcr The defect correction residuals associated with the current nonlinear
1346+
* iteration.
1347+
* @param use_picard Whether a Picard iteration was used to update the nonlinear
1348+
* iteration (true) or a Newton update (false).
1349+
* @param search_direction The proposed update direction for the solution vector.
1350+
*
1351+
* @return This function returns the updated residual after the line
1352+
* search is performed.
1353+
*
1354+
* This function is implemented in
1355+
* <code>source/simulator/helper_functions.cc</code>
1356+
*/
1357+
double perform_line_search(const DefectCorrectionResiduals &dcr,
1358+
const bool use_picard,
1359+
LinearAlgebra::BlockVector &search_direction);
13401360

13411361
/**
13421362
* Add constraints to the given @p constraints object that are required

source/simulator/helper_functions.cc

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,6 +2631,93 @@ namespace aspect
26312631

26322632

26332633

2634+
template <int dim>
2635+
double Simulator<dim>::perform_line_search(const DefectCorrectionResiduals &dcr,
2636+
const bool use_picard,
2637+
LinearAlgebra::BlockVector &search_direction)
2638+
{
2639+
LinearAlgebra::BlockVector backup_linearization_point(introspection.index_sets.stokes_partitioning, mpi_communicator);
2640+
double step_length_factor = 1.0;
2641+
unsigned int line_search_iteration = 0;
2642+
2643+
// Many parts of the solver depend on the block layout (velocity = 0,
2644+
// pressure = 1). For example the linearized_stokes_initial_guess vector or the StokesBlock matrix
2645+
// wrapper. Let us make sure that this holds (and shorten their names):
2646+
const unsigned int pressure_block_index = (parameters.include_melt_transport) ?
2647+
introspection.variable("fluid pressure").block_index
2648+
: introspection.block_indices.pressure;
2649+
const unsigned int velocity_block_index = introspection.block_indices.velocities;
2650+
Assert(velocity_block_index == 0, ExcNotImplemented());
2651+
Assert(pressure_block_index == 1, ExcNotImplemented());
2652+
(void) pressure_block_index;
2653+
2654+
// Do the loop for the line search at least once with the full step length.
2655+
// If line search is disabled we will exit the loop in the first iteration.
2656+
do
2657+
{
2658+
if (line_search_iteration == 0)
2659+
{
2660+
// backup our starting point for the line search
2661+
backup_linearization_point.block(pressure_block_index) = current_linearization_point.block(pressure_block_index);
2662+
backup_linearization_point.block(velocity_block_index) = current_linearization_point.block(velocity_block_index);
2663+
}
2664+
else
2665+
{
2666+
// undo the last iteration and try again with smaller step length
2667+
current_linearization_point.block(pressure_block_index) = backup_linearization_point.block(pressure_block_index);
2668+
current_linearization_point.block(velocity_block_index) = backup_linearization_point.block(velocity_block_index);
2669+
search_direction.block(pressure_block_index) *= step_length_factor;
2670+
search_direction.block(velocity_block_index) *= step_length_factor;
2671+
}
2672+
2673+
// Update the current linearization point with the search direction
2674+
current_linearization_point.block(pressure_block_index) += search_direction.block(pressure_block_index);
2675+
current_linearization_point.block(velocity_block_index) += search_direction.block(velocity_block_index);
2676+
2677+
// Rebuild the rhs to determine the new residual.
2678+
assemble_newton_stokes_matrix = rebuild_stokes_preconditioner = false;
2679+
rebuild_stokes_matrix = !boundary_velocity_manager.get_prescribed_boundary_velocity_indicators().empty();
2680+
2681+
assemble_stokes_system();
2682+
2683+
const double test_velocity_residual = system_rhs.block(velocity_block_index).l2_norm();
2684+
const double test_pressure_residual = system_rhs.block(pressure_block_index).l2_norm();
2685+
const double test_residual = std::sqrt(test_velocity_residual * test_velocity_residual
2686+
+ test_pressure_residual * test_pressure_residual);
2687+
2688+
// Determine if the residual has decreased sufficiently.
2689+
const double alpha = 1e-4;
2690+
if (test_residual < (1.0 - alpha * step_length_factor) * dcr.residual
2691+
||
2692+
line_search_iteration >= newton_handler->parameters.max_newton_line_search_iterations
2693+
||
2694+
use_picard)
2695+
{
2696+
pcout << " Newton system information: Norm of the rhs: " << test_residual
2697+
<< ", Derivative scaling factor: " << newton_handler->parameters.newton_derivative_scaling_factor << std::endl;
2698+
return test_residual;
2699+
}
2700+
else
2701+
{
2702+
pcout << " Line search iteration " << line_search_iteration << ", with norm of the rhs "
2703+
<< test_residual << " and going to " << (1.0 - alpha * step_length_factor) * dcr.residual
2704+
<< ", relative residual: " << test_residual/dcr.initial_residual << std::endl;
2705+
2706+
// The current search direction has not decreased the residual
2707+
// enough, so we take a smaller step and try again.
2708+
// TODO: make a parameter out of this.
2709+
step_length_factor = 2.0/3.0;
2710+
}
2711+
2712+
++line_search_iteration;
2713+
Assert(line_search_iteration <= newton_handler->parameters.max_newton_line_search_iterations,
2714+
ExcInternalError());
2715+
}
2716+
while (true);
2717+
}
2718+
2719+
2720+
26342721
template <int dim>
26352722
double
26362723
Simulator<dim>::compute_Eisenstat_Walker_linear_tolerance(const bool EisenstatWalkerChoiceOne,
@@ -2779,6 +2866,9 @@ namespace aspect
27792866
template void Simulator<dim>::restore_outflow_boundary_ids(const unsigned int boundary_id_offset); \
27802867
template void Simulator<dim>::check_consistency_of_boundary_conditions() const; \
27812868
template double Simulator<dim>::compute_initial_newton_residual(); \
2869+
template double Simulator<dim>::perform_line_search(const DefectCorrectionResiduals &dcr, \
2870+
const bool use_picard, \
2871+
LinearAlgebra::BlockVector &search_direction); \
27822872
template double Simulator<dim>::compute_Eisenstat_Walker_linear_tolerance(const bool EisenstatWalkerChoiceOne, \
27832873
const double maximum_linear_stokes_solver_tolerance, \
27842874
const double linear_stokes_solver_tolerance, \

source/simulator/solver_schemes.cc

Lines changed: 52 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ namespace aspect
530530
}
531531

532532

533+
533534
template <int dim>
534535
void Simulator<dim>::do_one_defect_correction_Stokes_step(DefectCorrectionResiduals &dcr,
535536
const bool use_picard)
@@ -547,6 +548,49 @@ namespace aspect
547548
Assert(!parameters.include_melt_transport
548549
|| introspection.variable("compaction pressure").block_index == 1, ExcNotImplemented());
549550

551+
// Define some lambda functions here for readability
552+
auto update_residuals = [&]()
553+
{
554+
dcr.velocity_residual = system_rhs.block(velocity_block_index).l2_norm();
555+
dcr.pressure_residual = system_rhs.block(pressure_block_index).l2_norm();
556+
dcr.residual = std::sqrt(dcr.velocity_residual * dcr.velocity_residual +
557+
dcr.pressure_residual * dcr.pressure_residual);
558+
};
559+
560+
// Eisenstat Walker method for determining the linear solver tolerance
561+
auto update_Eisenstat_Walker_tolerance = [&]()
562+
{
563+
const bool EisenstatWalkerChoiceOne = true;
564+
parameters.linear_stokes_solver_tolerance = compute_Eisenstat_Walker_linear_tolerance(EisenstatWalkerChoiceOne,
565+
newton_handler->parameters.maximum_linear_stokes_solver_tolerance,
566+
parameters.linear_stokes_solver_tolerance,
567+
dcr.stokes_residuals.second,
568+
dcr.residual,
569+
dcr.residual_old);
570+
571+
pcout << " The linear solver tolerance is set to "
572+
<< parameters.linear_stokes_solver_tolerance
573+
<< ". ";
574+
if (!use_picard)
575+
{
576+
pcout << "Stabilization Preconditioner is "
577+
<< Newton::to_string(newton_handler->parameters.preconditioner_stabilization)
578+
<< " and A block is "
579+
<< Newton::to_string(newton_handler->parameters.velocity_block_stabilization)
580+
<< '.';
581+
}
582+
pcout << std::endl;
583+
};
584+
585+
// build the preconditioner
586+
auto build_preconditioner = [&]()
587+
{
588+
if (stokes_matrix_free)
589+
stokes_matrix_free->build_preconditioner();
590+
else
591+
build_stokes_preconditioner();
592+
};
593+
550594
// Re-compute the pressure scaling factor for the Stokes assembly
551595
pressure_scaling = compute_pressure_scaling_factor();
552596

@@ -588,10 +632,7 @@ namespace aspect
588632

589633
assemble_stokes_system();
590634

591-
// recompute rhs
592-
dcr.velocity_residual = system_rhs.block(velocity_block_index).l2_norm();
593-
dcr.pressure_residual = system_rhs.block(pressure_block_index).l2_norm();
594-
dcr.residual = std::sqrt(dcr.velocity_residual * dcr.velocity_residual + dcr.pressure_residual * dcr.pressure_residual);
635+
update_residuals();
595636

596637
// Test whether the rhs has dropped so much that we can assume that the iteration is done.
597638
if (dcr.residual < dcr.residual_old * 1e-8)
@@ -607,38 +648,15 @@ namespace aspect
607648
}
608649

609650

610-
// Eisenstat Walker method for determining the linear solver tolerance
611651
if (nonlinear_iteration > 1)
612652
{
613653
if (!use_picard || newton_handler->parameters.use_Eisenstat_Walker_method_for_Picard_iterations)
614654
{
615-
const bool EisenstatWalkerChoiceOne = true;
616-
parameters.linear_stokes_solver_tolerance = compute_Eisenstat_Walker_linear_tolerance(EisenstatWalkerChoiceOne,
617-
newton_handler->parameters.maximum_linear_stokes_solver_tolerance,
618-
parameters.linear_stokes_solver_tolerance,
619-
dcr.stokes_residuals.second,
620-
dcr.residual,
621-
dcr.residual_old);
622-
623-
pcout << " The linear solver tolerance is set to "
624-
<< parameters.linear_stokes_solver_tolerance
625-
<< ". ";
626-
if (!use_picard)
627-
{
628-
pcout << "Stabilization Preconditioner is "
629-
<< Newton::to_string(newton_handler->parameters.preconditioner_stabilization)
630-
<< " and A block is "
631-
<< Newton::to_string(newton_handler->parameters.velocity_block_stabilization)
632-
<< '.';
633-
}
634-
pcout << std::endl;
655+
update_Eisenstat_Walker_tolerance();
635656
}
636657
}
637658

638-
if (stokes_matrix_free)
639-
stokes_matrix_free->build_preconditioner();
640-
else
641-
build_stokes_preconditioner();
659+
build_preconditioner();
642660

643661
// The solution of the defect correction solver is an update direction
644662
LinearAlgebra::BlockVector search_direction;
@@ -694,29 +712,16 @@ namespace aspect
694712
*/
695713
if (nonlinear_iteration > 1)
696714
{
697-
dcr.velocity_residual = system_rhs.block(velocity_block_index).l2_norm();
698-
dcr.pressure_residual = system_rhs.block(pressure_block_index).l2_norm();
699-
dcr.residual = std::sqrt(dcr.velocity_residual * dcr.velocity_residual + dcr.pressure_residual * dcr.pressure_residual);
715+
update_residuals();
700716

701717
// Eisenstat Walker method for determining the linear solver tolerance
702718
if (!use_picard)
703719
{
704-
const bool EisenstatWalkerChoiceOne = true;
705-
parameters.linear_stokes_solver_tolerance = compute_Eisenstat_Walker_linear_tolerance(EisenstatWalkerChoiceOne,
706-
newton_handler->parameters.maximum_linear_stokes_solver_tolerance,
707-
parameters.linear_stokes_solver_tolerance,
708-
dcr.stokes_residuals.second,
709-
dcr.residual,
710-
dcr.residual_old);
711-
712-
pcout << " The linear solver tolerance is set to " << parameters.linear_stokes_solver_tolerance << std::endl;
720+
update_Eisenstat_Walker_tolerance();
713721
}
714722
}
715723

716-
if (stokes_matrix_free)
717-
stokes_matrix_free->build_preconditioner();
718-
else
719-
build_stokes_preconditioner();
724+
build_preconditioner();
720725

721726
// Give this another try:
722727
dcr.stokes_residuals = solve_stokes(search_direction);
@@ -744,79 +749,11 @@ namespace aspect
744749
dcr.residual_old = dcr.residual;
745750

746751
// We have computed an update. Prepare for a line search.
747-
LinearAlgebra::BlockVector backup_linearization_point(introspection.index_sets.stokes_partitioning, mpi_communicator);
748-
749-
double step_length_factor = 1.0;
750-
unsigned int line_search_iteration = 0;
751-
752-
// Do the loop for the line search at least once with the full step length.
753-
// If line search is disabled we will exit the loop in the first iteration.
754-
do
755-
{
756-
if (line_search_iteration == 0)
757-
{
758-
// backup our starting point for the line search
759-
backup_linearization_point.block(pressure_block_index) = current_linearization_point.block(pressure_block_index);
760-
backup_linearization_point.block(velocity_block_index) = current_linearization_point.block(velocity_block_index);
761-
}
762-
else
763-
{
764-
// undo the last iteration and try again with smaller step length
765-
current_linearization_point.block(pressure_block_index) = backup_linearization_point.block(pressure_block_index);
766-
current_linearization_point.block(velocity_block_index) = backup_linearization_point.block(velocity_block_index);
767-
search_direction.block(pressure_block_index) *= step_length_factor;
768-
search_direction.block(velocity_block_index) *= step_length_factor;
769-
}
770-
771-
// Update the current linearization point with the search direction
772-
current_linearization_point.block(pressure_block_index) += search_direction.block(pressure_block_index);
773-
current_linearization_point.block(velocity_block_index) += search_direction.block(velocity_block_index);
774-
775-
// Rebuild the rhs to determine the new residual.
776-
assemble_newton_stokes_matrix = rebuild_stokes_preconditioner = false;
777-
rebuild_stokes_matrix = !boundary_velocity_manager.get_prescribed_boundary_velocity_indicators().empty();
778-
779-
assemble_stokes_system();
780-
781-
const double test_velocity_residual = system_rhs.block(velocity_block_index).l2_norm();
782-
const double test_pressure_residual = system_rhs.block(pressure_block_index).l2_norm();
783-
const double test_residual = std::sqrt(test_velocity_residual * test_velocity_residual
784-
+ test_pressure_residual * test_pressure_residual);
785-
786-
// Determine if the residual has decreased sufficiently.
787-
const double alpha = 1e-4;
788-
if (test_residual < (1.0 - alpha * step_length_factor) * dcr.residual
789-
||
790-
line_search_iteration >= newton_handler->parameters.max_newton_line_search_iterations
791-
||
792-
use_picard)
793-
{
794-
pcout << " Newton system information: Norm of the rhs: " << test_residual
795-
<< ", Derivative scaling factor: " << newton_handler->parameters.newton_derivative_scaling_factor << std::endl;
796-
dcr.residual = test_residual;
797-
break;
798-
}
799-
else
800-
{
801-
pcout << " Line search iteration " << line_search_iteration << ", with norm of the rhs "
802-
<< test_residual << " and going to " << (1.0 - alpha * step_length_factor) * dcr.residual
803-
<< ", relative residual: " << test_residual/dcr.initial_residual << std::endl;
804-
805-
// The current search direction has not decreased the residual
806-
// enough, so we take a smaller step and try again.
807-
// TODO: make a parameter out of this.
808-
step_length_factor = 2.0/3.0;
809-
}
810-
811-
++line_search_iteration;
812-
Assert(line_search_iteration <= newton_handler->parameters.max_newton_line_search_iterations,
813-
ExcInternalError());
814-
}
815-
while (true);
752+
dcr.residual = perform_line_search(dcr, use_picard, search_direction);
816753
}
817754

818755

819-
if (use_picard == true)
756+
if (use_picard)
820757
{
821758
// When we are using (defect corrected) Picard, keep the
822759
// newton_derivative_scaling_factor at zero. The newton_derivative_scaling_factor

0 commit comments

Comments
 (0)