Projection

Projection

Many NEP-solvers are based on a computation of a solution to a projected problem, i.e., if $V,W\in\mathbb{R}^{n\times p}$ we need to solve the (smaller) NEP

\[W^HM(λ)Vz=0\]

This is sometimes called a nonlinear Rayleigh-Ritz procedure, or a direct projection. These are inner solvers for many NEP-solvers.

NEP-PACK provides a framework to handle projected problems and inner solves. This is implemented into two separate components:

As a NEP-user, you often do not need to care about how the projection is handled, e.g., if you use the type SPMF_NEP with only a few terms. For instance, if you wish to use the infinite Arnoldi method (iar) to handle the project solves in the nonlinear Arnoldi method (nlar), you can call nlar with the kwarg inner_solver_method=IARInnerSolver():

julia> nep=nep_gallery("dep0_tridiag");
julia> λ,v=nlar(nep,neigs=1,inner_solver_method=IARInnerSolver(),logger=1);
Using inner solver IARInnerSolver(1.0e-13, 80, :ones, false, NonlinearEigenproblems.NEPSolver.iar)
iter 1 err:0.07075594099201046 λ=-0.09271600160844096 + 0.03235352263155604im
iter 2 err:0.03648528716335285 λ=0.1565592513189133 + 0.013613366856692472im
iter 3 err:0.026208926915836202 λ=-0.03227579276149921 - 0.02208846862836847im
iter 4 err:0.007172980379743359 λ=-0.05583493095814305 - 0.003087442900200613im
iter 5 err:0.006924911617556792 λ=-0.05401508521132263 + 0.001517986336058301im
iter 6 err:0.002090878656094346 λ=-0.05536501763852141 + 0.00024284078743963842im
iter 7 err:0.0006103719429985026 λ=-0.05575237680802954 - 0.00013474280139834488im
iter 8 err:0.0002665363571995074 λ=-0.05558344084437247 + 6.558335069789856e-6im
iter 9 err:0.023093036823993347 λ=-0.02304829345220316 + 0.0005911146839749534im
iter 10 err:1.092985097378946e-5 λ=-0.055653357185449566 - 2.575336159079228e-6im
iter 11 err:5.316449394914625e-7 λ=-0.05565515933487699 + 2.446717728666673e-7im
iter 12 err:0.016299715925824968 λ=0.023211712543947764 - 0.033813269858071315im
iter 13 err:4.899222816433482e-8 λ=-0.05565520569884035 - 2.0175841868490324e-8im
iter 14 err:4.320444084682331e-9 λ=-0.055655212526421895 + 1.2690665519371834e-9im
iter 15 err:7.639277885601529e-10 λ=-0.05565521363070881 + 3.971813908747728e-11im
iter 16 err:4.941632484271334e-11 λ=-0.055655213920101095 - 7.493472629149413e-12im
iter 17 err:0.010663980713333146 λ=0.008967322670902417 + 0.016345149953039387im
iter 18 err:3.2317477531099844e-12 λ=-0.05565521393097803 - 6.246051566770699e-13im
iter 19 err:2.3964655361108506e-13 λ=-0.05565521393184677 + 6.221189257479347e-14im
iter 20 err:1.1735483724254833e-13 λ=-0.055655213931828935 + 2.058434802154811e-15im
iter 21 err:8.164760090914088e-15 λ=-0.055655213931847865 + 7.498992677576017e-16im
****** 1 converged to eigenvalue: -0.055655213931847865 + 7.498992677576017e-16im errmeasure:8.164760090914088e-15

The logging of the inner solver is controlled by the kwarg inner_logger, which follows the same framework as the standard NEP-PACK Logger. This produces very verbose output illustrating also the convergence of the inner solve:

julia> λ,v=nlar(nep,neigs=1,inner_solver_method=IARInnerSolver(),logger=1,inner_logger=1);

Using inner solver IARInnerSolver(1.0e-13, 80, :ones, false, NonlinearEigenproblems.NEPSolver.iar)
-
--
---
----
-----
------
=------
+-------
iter 1 err:0.06907648709827012 λ=-0.13302652304722704 + 0.0499583011875092im
-
--
---
----
-----
------
=------
+-------
iter 2 err:0.03702238592099922 λ=0.08696844790344242 + 0.010741310688729204im
-
--
---
----
-----
------
-------
=-------
+=-------
iter 3 err:0.029408773621139236 λ=0.0076466477038438325 - 0.07172981749577159im
-
--
---
----
-----
------
-------
--------
+--------
...

Rayleigh functional computation, which corresponds to projection with $p=1$, is also handled with this framework.

Inner solvers

The inner solvers inherit from InnerSolver. The following inner solvers are available by default.

struct NewtonInnerSolver
function NewtonInnerSolver(;tol=1e-13,maxit=80,starting_vector=:Vk,
                           newton_function=augnewton)

Uses a Newton-like method to solve the inner problem, with tolerance, and maximum number of iterations given by tol and maxit. The starting vector can be :ones, :randn, or :Vk. The value :Vk specifies the use of the outer NEP-solver keyword argument (Vk). This is typically the previous iterate in the outer method.

The kwarg newton_function, specifies a Function which is called. The type supports augnewton, newton, resinv quasinewton, newtonqr. In principle it can be any method which takes the same keyword arguments as these methods.

See also: InnerSolver, inner_solve

struct IARInnerSolver
function IARInnerSolver(;tol=1e-13,maxit=80,
           starting_vector=:ones,normalize_DEPs=:auto,
           iar_function=iar)

Uses iar, tiar or iar_chebyshev to solve the inner problem, with tolerance, and maximum number of iterations given by tol and maxit. The starting vector can be :ones or :randn. The iar_function can be iar, tiar or iar_chebyshev (or any function taking the same parameters as input). normalize_DEPs determines if the we should carry out precomputation and make sure the projection of a DEP is again a DEP (which can speed up performance). It can take the value true, false or :auto. :auto sets it to true if we use the iar_chebyshev solver.

The kwarg iar_function, specifies a Function which is called. Examples of functions are iar and iar_chebyshev. It can be any NEP-solver which takes the same keyword arguments as these methods.

See also: InnerSolver, inner_solve

function IARChebInnerSolver(;tol=1e-13,maxit=80,starting_vector=:ones,
                            normalize_DEPs=true)

Uses iar_chebyshev to solve the inner problem. See IARInnerSolver for keyword argument documentation.

See also: IARInnerSolver, InnerSolver, inner_solve

struct ContourBeynInnerSolver <: InnerSolver
function ContourBeynInnerSolver(;tol=sqrt(eps(real(Float64))),
                                radius=:auto,N=1000)

Uses contour_beyn to solve the inner problem, with radius and number of quadrature nodes, given by radius and n. If the variable radius is set to :auto, the integration radius will be automatically selected by using the eigenvalue approximations specified by the outer solver.

See also: InnerSolver, inner_solve

struct PolyeigInnerSolver <: InnerSolver
function PolyeigInnerSolver()

Specifies the use of polyeig to solve the inner problem. This is intended for NEPs of the type PEP.

See also: InnerSolver, inner_solve

struct SGIterInnerSolver <: InnerSolver

Uses sgiter to solve the inner problem.

See also: InnerSolver, inner_solve

struct NleigsInnerSolver <: InnerSolver
function NleigsInnerSolver(;Σ= :auto,nodes =:auto, tol=1e-6 )

Uses nleigs to solve the inner problem, in the region Σ with shifts nodes and with tolerance tol. If the variable Σ is set to :auto, the region Σ will be set by using the eigenvalues approximations. See nleigs for description of parameters.

See also: InnerSolver, inner_solve

struct DefaultInnerSolver <: InnerSolver

Dispatches a version of inner_solve based on the type of the NEP provided. This function tries to automatically detect which solver is recommended.

See also: InnerSolver, inner_solve

Inner solvers: Advanced usage

You can define your own inner solver by inheriting from InnerSolver and implementing the function inner_solve. Since the inner_solve obtains information from the solver via keyword arguments, you need to end your method signature with kwargs...).

abstract type InnerSolver

Structs inheriting from this type are used to solve inner problems in an inner-outer iteration.

The InnerSolver objects are passed to the NEP-algorithms, which uses it to dispatch the correct version of the function inner_solve. Utilizes existing implementations of NEP-solvers and inner_solve acts as a wrapper to these.

Example

There is a DefaultInnerSolver that dispatches an inner solver based on the provided NEP. However, this example shows how you can force nlar to use the IARInnerSolver for a PEP.

julia> nep=nep_gallery("pep0", 100);
julia> λ,v = nlar(nep, inner_solver_method=NEPSolver.IARInnerSolver(), neigs=1, num_restart_ritz_vecs=1, maxit=70, tol=1e-8);
julia> norm(compute_Mlincomb(nep,λ[1],vec(v)))
8.68118417430353e-9

See also: inner_solve, DefaultInnerSolver, NewtonInnerSolver, PolyeigInnerSolver, IARInnerSolver, IARChebInnerSolver, SGIterInnerSolver, ContourBeynInnerSolver

inner_solve(is::InnerSolver,T_arit,nep;kwargs...)

Solves the projected linear problem with solver specied with is. This is to be used as an inner solver in an inner-outer iteration. T specifies which method to use. The most common choice is DefaultInnerSolver. The function returns (λv,V) where λv is an array of eigenvalues and V a matrix with corresponding vectors. The struct T_arit defines the arithmetics used in the outer iteration and should prefereably also be used in the inner iteration.

Different inner_solve methods take different kwargs. These are standardized kwargs:

  • neigs: Number of wanted eigenvalues (but less or more may be returned)
  • σ: target specifying where eigenvalues
  • λv, V: Vector/matrix of guesses to be used as starting values
  • j: the jth eigenvalue in a min-max characterization
  • tol: Termination tolarance for inner solver
  • inner_logger: Determines how the inner solves are logged. See Logger for further references

Projection

The NEP-PACK functionality for projected problems are represented by projection types. Normally, the projection is created by create_proj_NEP from a standard NEP. After creating a projected NEP, you can set the projection subspace (represented by the matrices V and W) using set_projectmatrices! or expand_projectmatrices!.

julia> A=[1 0 0; 0 1.0 0; 0 0 1]; B=[1 2 3; 3 3 3 ; 4 -1 -1.0];
julia> nep=SPMF_NEP([A, B], [s->s, s->s^5]);
julia> pnep=create_proj_NEP(nep);
julia> W=[4 1 ; 6 1  ; 6.0 2]; V=[3 3;3 4.0;4.0 -1];
julia> set_projectmatrices!(pnep,W,V); # modifies pnep
julia> λ=3.0+1im;
julia> W'*compute_Mder(nep,λ)*V
2×2 Array{Complex{Float64},2}:
 -3366.0+92958.0im  -2238.0+61334.0im
  -690.0+19290.0im   -513.0+13909.0im
julia> compute_Mder(pnep,λ)
2×2 Array{Complex{Float64},2}:
 -3366.0+92958.0im  -2238.0+61334.0im
  -690.0+19290.0im   -513.0+13909.0im

Effectively, the Proj_NEP creates compute functions, which are designed to be as efficient as possible.

Projection functions

You can create a projected NEP with create_proj_NEP, and specify the projection space with set_projectmatrices! and expand_projectmatrices!.

pnep=create_proj_NEP(orgnep::ProjectableNEP[,maxsize [,T]])

Create a NEP representing a projected problem $N(λ)=W^HM(λ)V$, where the base NEP is represented by orgnep. The optional parameter maxsize::Int determines how large the projected problem can be and T is the Number type used for the projection matrices (default ComplexF64). These are needed for memory preallocation reasons. Use set_projectmatrices! and expand_projectmatrices! to specify projection matrices $V$ and $W$.

Example:

The following example illustrates that a projection of a NEP is also a NEP and we can for instance call compute_Mder on it:

julia> nep=nep_gallery("pep0")
julia> V=Matrix(1.0*I,size(nep,1),2);
julia> W=Matrix(1.0*I,size(nep,1),2);
julia> pnep=create_proj_NEP(nep);
julia> set_projectmatrices!(pnep,W,V);
julia> compute_Mder(pnep,3.0)
2×2 Array{Complex{Float64},2}:
  6.08082+0.0im  -5.47481+0.0im
 0.986559+0.0im  -6.98165+0.0im
julia> W'*compute_Mder(nep,3.0)*V  # Gives the same result
2×2 Array{Float64,2}:
 6.08082   -5.47481
 0.986559  -6.98165

If you know that you will only use real projection matrices, you can specify this in at the creation:

julia> pnep=create_proj_NEP(nep,2,Float64);
julia> set_projectmatrices!(pnep,W,V);
julia> compute_Mder(pnep,3.0)
2×2 Array{Float64,2}:
 6.08082   -5.47481
 0.986559  -6.98165
set_projectmatrices!(pnep::Proj_NEP,W,V)

Set the projection matrices for the NEP to W and V, i.e., corresponding the NEP: $N(λ)=W^HM(λ)V$. See also create_proj_NEP.

Example

This illustrates if W and V are vectors of ones, the projected problem becomes the sum of the rows and columns of the original NEP.

julia> nep=nep_gallery("pep0")
julia> pnep=create_proj_NEP(nep);
julia> V=ones(200,1);  W=ones(200,1);
julia> set_projectmatrices!(pnep,W,V);
julia> compute_Mder(pnep,0)
1×1 Array{Complex{Float64},2}:
 48.948104019482756 + 0.0im
julia> sum(compute_Mder(nep,0),dims=[1,2])
1×1 Array{Float64,2}:
 48.948104019482955
expand_projectmatrices!(nep::Proj_SPMF_NEP, Wnew, Vnew)

The projected NEP is updated by adding the last column of Wnew and Vnew to the basis. Note that Wnew and Vnew contain also the "old" basis vectors. See also create_proj_NEP

Example:

In the following example you see that the expanded projected problem has one row and column more, and the leading subblock is the same as the smaller projected NEP.

julia> nep=nep_gallery("pep0"); n=size(nep,1);
julia> V=Matrix(1.0*I,n,2); W=Matrix(1.0*I,n,2);
julia> pnep=create_proj_NEP(nep);
julia> set_projectmatrices!(pnep,W,V);
julia> compute_Mder(pnep,0)
2×2 Array{Complex{Float64},2}:
 0.679107+0.0im   -0.50376+0.0im
 0.828413+0.0im  0.0646768+0.0im
julia> Vnew=[V ones(n)]
julia> Wnew=[W ones(n)]
julia> expand_projectmatrices!(pnep,Wnew,Vnew);
julia> compute_Mder(pnep,0)
3×3 Array{Complex{Float64},2}:
 0.679107+0.0im   -0.50376+0.0im  -12.1418+0.0im
 0.828413+0.0im  0.0646768+0.0im   16.3126+0.0im
 -17.1619+0.0im   -10.1628+0.0im   48.9481+0.0im

Projection types

NEPs for which this projection can be computed inherit from ProjectableNEP.

abstract ProjectableNEP <: NEP

A ProjectableNEP is a NEP which can be projected, i.e., one can construct the problem $W'*M(λ)Vw=0$ with the Proj_NEP. A NEP which is of this must have the function create_proj_NEP(orgnep::ProjectableNEP) implemented. This function must return a Proj_NEP.

See also: set_projectmatrices!.

Example:

julia> nep=nep_gallery("dep0");
julia> typeof(nep)
DEP{Float64,Array{Float64,2}}
julia> isa(nep,ProjectableNEP)
true
julia> projnep=create_proj_NEP(nep);
julia> e1 = Matrix(1.0*I,size(nep,1),1);
julia> set_projectmatrices!(projnep,e1,e1);
julia> compute_Mder(nep,3.0)[1,1]
-2.942777908030041
julia> compute_Mder(projnep,3.0)
1×1 Array{Complex{Float64},2}:
 -2.942777908030041 + 0.0im

The result of the projection is represented in a Proj_NEP.

abstract type Proj_NEP <: NEP

Proj_NEP represents a projected NEP. The projection is defined as the NEP

\[N(λ)=W^HM(λ)V\]

where $M(λ)$ is a base NEP and W and V rectangular matrices representing a basis of the projection spaces. Instances are created with create_proj_NEP. See create_proj_NEP for examples.

Any Proj_NEP needs to implement two functions to manipulate the projection:

One explicit instance is the Proj_SPMF_NEP.

struct Proj_SPMF_NEP <: Proj_NEP

This type represents the (generic) way to project NEPs which are AbstractSPMF. See examples in create_proj_NEP.

Rayleigh functional computation

compute_rf(eltype::Type,nep::NEP,x,inner_solver::InnerSolver;
           y=x, target=zero(T), λ0=target,TOL=eps(real(T))*1e3,max_iter=10)

Computes the Rayleigh functional of the nep, i.e., computes a vector $Λ$ of values $λ$ such that $y^TM(λ)x=0$, using the procedure specified in inner_solver. The default behaviour consists of a scalar valued Newton-iteration, and the returned vector has only one element.

The given eltype<:Number is the type of the returned vector.

Example

This uses iar to solve the (scalar) nonlinear problem.

julia> nep=nep_gallery("dep0");
julia> x=ones(size(nep,1));
julia> s=compute_rf(ComplexF64,nep,x,IARInnerSolver())[1] # Take just first element
-1.186623627962043 - 1.5085094961223182im
julia> x'*compute_Mlincomb(nep,s,x)
-8.881784197001252e-16 + 1.0880185641326534e-14im

To the top