Program Listing for File NewtonSolver.m

Return to documentation for file (+Indigo/NewtonSolver.m)

%
%> Class container for a dumped Newton's method with affine invariant step.
%>
%> **Solution Algorithm:**
%>
%> Given a zeros of a vectorial function problem of the form \f$ \mathbf{F}
%> (\mathbf{x}) = \mathbf{0} \f$, where \f$ \mathbf{F}: \mathbb{R}^n \rightarrow
%> \mathbb{R}^n \f$, then the Newton's method is defined as:
%>
%> \f[
%> \mathbf{JF}(\mathbf{x}_k)\mathbf{h} = -\mathbf{F}(\mathbf{x}_k).
%> \f]
%>
%> The dumped step is defined as:
%>
%> \f[
%> \mathbf{x}_{k+1} = \mathbf{x}_k + \alpha_k \mathbf{h}
%> \f]
%>
%> where \f$ \alpha_k \f$ is a dumping coefficient that satisfies:
%>
%> \f[
%> \left\| \mathbf{JF}(\mathbf{x}_k)^{-1} \mathbf{F}(\mathbf{x}_{k+1}) \right\|
%> \leq \left(1 - \dfrac{\alpha_k}{2}\right) \left\| \mathbf{JF}(\mathbf{x}_k)^{-1}
%> \mathbf{F}(\mathbf{x}_k) \right\| = \left(1 - \dfrac{\alpha_k}{2} \right)
%> \left\| \mathbf{h}  \right\|.
%> \f]
%>
%> **Note:**
%>
%> For more details on the Newton's method with affine invariant step refer to:
%> https://www.zib.de/deuflhard/research/algorithm/ainewton.en.html.
%
classdef NewtonSolver < handle
  %
  properties (Access = private)
    %
    %> Function handle.
    %
    m_function_handle;
    %
    %> Jacobian handle.
    %
    m_jacobian_handle;
    %
    %> Algorithm tolerance.
    %
    m_tolerance = 1.0e-10;
    %
    %> Maximum allowed algorithm iterations.
    %
    m_max_iterations = 100;
    %
    %> Maximum allowed algorithm relaxations.
    %
    m_max_relaxations = 50;
    %
    %> Verbose mode boolean.
    %
    m_verbose = false;
    %
    %> Algorithm iterations.
    %
    m_iterations = 0;
    %
    %> Function evaluations.
    %
    m_function_evaluations = 0;
    %
    %> Jacobian evaluations.
    %
    m_jacobian_evaluations = 0;
    %
    %> Algorithm relaxations.
    %
    m_relaxations = 0;
    %
    %> Function residuals.
    %
    m_residuals = 0.0;
    %
    %> Convergence boolean.
    %
    m_converged = false;
    %
    %> Relaxation factor.
    %
    m_alpha = 0.8;
    %
  end
  %
  methods
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Newton's solver class constructor.
    %>
    %> \return The Newton's solver object.
    %
    function this = NewtonSolver()
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set algorithm tolerance.
    %>
    %> \param t_tolerance The algorithm tolerance.
    %
    function set_tolerance( this, t_tolerance )

      CMD = 'Indigo.NewtonSolver.set_tolerance(...): ';

      assert( ...
        ~isnan(t_tolerance) && ...
        isfinite(t_tolerance) && ...
        t_tolerance > 0.0, ...
        [CMD, 'invalid input detected.']);

      this.m_tolerance = t_tolerance;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get algorithm tolerance.
    %>
    %> \return The algorithm tolerance.
    %
    function out = get_tolerance( this )
      out = this.m_tolerance;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set maximum allowed algorithm iterations.
    %>
    %> \param t_max_iterations The maximum allowed algorithm iterations.
    %
    function set_max_iterations( this, t_max_iterations )

      CMD = 'Indigo.NewtonSolver.set_max_iterations(...): ';

      assert( ...
        ~isnan(t_max_iterations) && ...
        isfinite(t_max_iterations) && ...
        t_max_iterations > 0, ...
        [CMD, 'invalid input detected.']);

      this.m_max_iterations = t_max_iterations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set maximum allowed algorithm iterations.
    %>
    %> \return The maximum allowed algorithm iterations.
    %
    function out = get_max_iterations( this )
      out = this.m_max_iterations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set maximum allowed algorithm relaxations.
    %>
    %> \param t_max_relaxations The maximum allowed algorithm relaxations.
    %
    function set_max_relaxations( this, t_max_relaxations )

      CMD = 'Indigo.NewtonSolver.set_max_relaxations(...): ';

      assert( ...
        ~isnan(t_max_relaxations) && ...
        isfinite(t_max_relaxations) && ...
        t_max_relaxations > 0, ...
        [CMD, 'invalid input detected.']);

      this.m_max_evaluations = t_max_relaxations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get maximum allowed algorithm relaxations.
    %>
    %> \return The maximum allowed algorithm relaxations.
    %
    function out = get_max_relaxations( this )
      out = this.m_max_relaxations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set relaxation factor.
    %>
    %> \param t_alpha The relaxation factor.
    %
    function set_alpha( this, t_alpha )

      CMD = 'Indigo.NewtonSolver.set_alpha(...): ';

      assert(~isnan(t_alpha) && isfinite(t_alpha) && 0.0 < t_alpha && t_alpha < 1.0, ...
        [CMD, 'invalid input detected.']);

      this.m_alpha = t_alpha;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get relaxation factor.
    %>
    %> \return The relaxation factor.
    %
    function out = get_alpha( this )
      out = this.m_alpha;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Enable verbose mode.
    %>
    %> \param t_alpha The relaxation factor.
    %
    function enable_verbose( this )
      this.m_verbose = true;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Disable verbose mode.
    %>
    %> \param t_alpha The relaxation factor.
    %
    function disable_verbose( this )
      this.m_verbose = false;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get algorithm iterations.
    %>
    %> \return The algorithm iterations.
    %
    function out = out_iterations( this )
      out = this.m_iterations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set function evaluations.
    %>
    %> \return The function evaluations.
    %
    function out = out_function_evaluations( this )
      out = this.m_function_evaluations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Set Jacobian evaluations.
    %>
    %> \return The Jacobian evaluations.
    %
    function out = out_jacobian_evaluations( this )
      out = this.m_jacobian_evaluations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get algorithm relaxations.
    %>
    %> \return The algorithm relaxations.
    %
    function out = out_relaxations( this )
      out = this.m_relaxations;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get function evaluations.
    %>
    %> \return The function evaluations.
    %
    function out = out_residuals( this )
      out = this.m_residuals;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Get convergence boolean value.
    %>
    %> \return The convergence boolean value.
    %
    function out = out_converged( this )
      out = this.m_converged;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Solve non-linear system of equations \f$ \mathbf{F}(\mathbf{x}) =
    %> \mathbf{0} \f$
    %>
    %> \param t_function_handle The function handle.
    %> \param t_jacobian_handle The Jacobian handle.
    %> \param x_ini             The initial guess vector \f$ \mathbf{x} \f$.
    %>
    %> \return The solution vector \f$ \mathbf{x} \f$.
    %
    function [out, ierr] = solve_handle( this, t_function_handle, t_jacobian_handle, x_ini )
      this.m_function_handle = t_function_handle;
      this.m_jacobian_handle = t_jacobian_handle;
      [out, ierr] = this.solve(x_ini);
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Reset solver internal counter and variables.
    %>
    %> \param t_function_handle The function handle.
    %
    function reset( this )
      this.m_iterations           = 0;
      this.m_function_evaluations = 0;
      this.m_jacobian_evaluations = 0;
      this.m_relaxations          = 0;
      this.m_residuals            = 0.0;
      this.m_converged            = false;
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Perform function \f$ \mathbf{F}(\mathbf{x}) \f$ evaluation.
    %>
    %> \param x The input vector \f$ \mathbf{x} \f$.
    %>
    %> \return The function value \f$ \mathbf{F}(\mathbf{x}) \f$.
    %
    function out = eval_function( this, x )
      this.m_function_evaluations = this.m_function_evaluations + 1;
      out = this.m_function_handle(x);
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Perform function \f$ \mathbf{JF}(\mathbf{x}) \f$ evaluation.
    %>
    %> \param x The input vector \f$ \mathbf{x} \f$.
    %>
    %> \return The Jacobian value \f$ \mathbf{JF}(\mathbf{x}) \f$.
    %
    function out = eval_jacobian( this, x )
      this.m_jacobian_evaluations = this.m_jacobian_evaluations + 1;
      out = this.m_jacobian_handle(x);
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
    %> Solve non-linear system of equations \f$ \mathbf{F} (\mathbf{x}) =
    %> \mathbf{0} \f$.
    %>
    %> \param x_ini The initial guess for the vector \f$ \mathbf{x} \f$.
    %>
    %> \return The solution \f$ \mathbf{x} \f$ and the output flag:
    %>         \f$ 0 \f$ = success,
    %>         \f$ 1 \f$ = failed because of bad initial point,
    %>         \f$ 2 \f$ = failed because of bad dumping (step got too short).
    %
    function [x, ierr] = solve( this, x_ini )

      CMD = 'Indigo.NewtonSolver.solve(...): ';

      % Setup internal variables
      this.reset();

      % Set initial iteration
      ierr = 1;
      x    = x_ini;

      % Algorithm iterations
      this.m_converged = false;
      for i=1:this.m_max_iterations
        this.m_iterations = i;

        if ~all(isfinite(x)); break; end

        % Evaluate F
        F = this.eval_function(x);
        if ~all(isfinite(F)); break; end

        % Check convergence
        if norm(F, inf) < this.m_tolerance
          ierr             = 0;
          this.m_converged = true;
          break;
        end

        % Evaluate JF
        J = this.eval_jacobian(x);
        if ~all(isfinite(J)); break; end

        % Evaluate advancing direction
        if (rcond(J) > 1e-8)
          D = -J\F;
        else
          [D, ~] = lsqr(J, -F, 1e-8, 50);
        end
        if ~all(isfinite(D)); break; end

        % Relax the iteration process
        tau    = 1/this.m_alpha;
        dumped = false;
        for j = 1:this.m_max_relaxations
          this.m_relaxations = j;

          tau = tau * this.m_alpha;

          % Update point
          x_dump = x + tau * D;
          F_dump = this.eval_function(x_dump);

          if ~all(isfinite(F_dump)); continue; end

          if (rcond(J) > 1e-8)
            D_dump = -J\F_dump;
          else
            [D_dump, ~] = lsqr(J, -F_dump, 1e-8, 50);
          end
          if ~all(isfinite(D_dump)); continue; end

          % Check relaxation convergence
          if (norm(D_dump, 2) <= eps + (1-tau/2) * norm(D, 2))
            dumped = true;
            break;
          end
        end

        % Check if dumping failed
        if ~dumped
          if this.m_verbose
            fprintf(1, [CMD, 'tau = %d, failed dumping iteration.\n'], tau);
          end
          break;
        end

        % Update solution
        x = x_dump;
        if this.m_verbose
          fprintf(1, '%s iter %d: ||F||_inf = %f, tau = %1.4f.\n', CMD, i, norm(F, inf), tau);
        end

        % Check convergence
        %if i > 2 && norm(D, inf) < this.m_tolerance
        %  this.m_converged = true;
        %  break;
        %end
      end
    end
    %
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    %
  end
end