I know that Eigenvalues is already quite well implemented in Mathematica, nor am I foolishly trying to improve on it. In order to improve my programming skills, I am trying to write Mathematica-style code to locate eigenvalues of a tridiagonal symmetric matrix using bisection. This is what I came up with.
tridiagbisec[diag_List, subdiag_List, tol_Real] :=
Module[{diagonals =
Split[Transpose[{diag, Join[{0}, subdiag]}], #2[[2]] =!= 0 &]},
Flatten[
bisecnonzero[-myNorm[Sequence @@ Transpose[#]],
myNorm[Sequence @@ Transpose[#]], tol, #] & /@ diagonals]] /;
Length[diag] - Length[subdiag] == 1
tridiagbisec is the main function I will be calling. It builds an array (diagonals) from the arrays containing the matrix elements of both the leading diagonal and the subdiagonal, then Splits it wherever a zero is found so that each block is evaluated separately, using bisecnonzero.
bisecnonzero[\[Lambda]min_Real, \[Lambda]max_Real, tol_Real, diagonals_List] :=
Module[{\[Lambda]med = (\[Lambda]min + \[Lambda]max)/2,
nmin = numeig[diagonals, \[Lambda]min],
nmax = numeig[diagonals, \[Lambda]max], nmed},
nmed = numeig[diagonals, \[Lambda]med];
{Which[(nmin > nmed) && (\[Lambda]med - \[Lambda]min > tol),
bisecnonzero[\[Lambda]min, \[Lambda]med, tol, diagonals],
(nmin > nmed) && (\[Lambda]med - \[Lambda]min <= tol),
ConstantArray[(\[Lambda]med + \[Lambda]min)/2, nmin - nmed],
True, {}],
Which[(nmed > nmax) && (\[Lambda]max - \[Lambda]med > tol),
bisecnonzero[\[Lambda]med, \[Lambda]max, tol, diagonals],
(nmed > nmax) && (\[Lambda]max - \[Lambda]med <= tol),
ConstantArray[(\[Lambda]max + \[Lambda]med)/2, nmed - nmax],
True, {}]}
]
This, I believe, is where I may have built my program in a suboptimal way. Are two Which the right way to iterate bisection?
numeig[diagonals_, \[Lambda]_] :=
numeig[diagonals, \[Lambda]] =
Unitize[#].UnitStep[#] &@
Rest@FoldList[
If[Not@PossibleZeroQ@#1, #2[[
1]] - \[Lambda] - #2[[2]]^2/#1, +\[Infinity]] &, 1, diagonals];
numeig computes the number of eigenvalues greater than \[Lambda] (cfr. (5) in Barth, Martin and Wilkinson (1967).
myNorm = Max[Abs@#1 + Abs@#2 + RotateLeft@Abs@#2] &;
I know that I am also reimplementing Norm[#,Infinity] &, but for scholastic purposes I think it may be useful, since for a tridiagonal matrix it has a particularly simple form and this way I can avoid building a SparseArray structure at all.
Since I am trying to be performance-conscious, how could the whole algorithm be improved upon with reasonable effort? ...Besides using Eigenvalues, that is! :D
Riffle[]? I seem to recall writing a bisection routine in Mathematica a while back; let me see if I can find it... – J. M.♦ Jun 20 '12 at 8:04numeigunpacks! And it seems it's becauseFoldListunpacks! This does not look nice. – Andrea Colonna Jun 20 '12 at 21:19