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
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:
- Projection: As a user (or NEP-solver developer) you can create a new object corresponding to the projection. In NEP-PACK, the projection is again an object of with type inheriting from
NEP
. More precisely, it is aProj_NEP
which you normally create with the functioncreate_proj_NEP
. - Inner solvers: Since the projected problem is again a
NEP
, in principle any of the NEP-solvers of this package can be used. This is handled by theInnerSolver
objects which are wrappers for corresponding NEP-solvers such that we can pass appropriate parameters to the inner soler. The inner solver is controlled by theinner_solver_method
keyword in many NEP-solvers. By defaultDefaultInnerSolver
is used.
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
NonlinearEigenproblems.NEPSolver.inner_solve
— Function.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 valuesj
: the jth eigenvalue in a min-max characterizationtol
: Termination tolarance for inner solverinner_logger
: Determines how the inner solves are logged. SeeLogger
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!
.
NonlinearEigenproblems.NEPTypes.create_proj_NEP
— Function.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
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:
set_projectmatrices!
: Set matricesW
andV
expand_projectmatrices!
: Effectively expand the matricesW
andV
with one column.
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
NonlinearEigenproblems.NEPSolver.compute_rf
— Function.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