Skip to content

WIP: Implement Cholesky-based OPs #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 23, 2023
Merged

WIP: Implement Cholesky-based OPs #63

merged 10 commits into from
Jan 23, 2023

Conversation

TSGut
Copy link
Member

@TSGut TSGut commented Aug 13, 2021

Starting to properly work on this now @dlfivefifty. Might also be of interest to @MarcoFasondini. Here's an example of what works so far:

    P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])
    x = axes(P,1)
    J = jacobimatrix(P)
    Jx = symmjacobim(J)
    t = 1.014
    w = (t*I - Jx^3)
    # compute Jacobi matrix via cholesky
    Jchol = cholesky_jacobimatrix(w)
    # compute Jacobi matrix via Lanczos
    wf = (t .- x.^3)
    Jlanc = jacobimatrix(LanczosPolynomial(@.(wf),Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])))
    # Comparison
    @test Jchol[1:500,1:500]  Jlanc[1:500,1:500]

Since it's intended to be used for normalised OPs, I'm using symmetry to store the Jacobi operator's bands as a (2,ℵ₀) cached array. It doesn't actually at all need the shift that I use in the tests - those only are there at the moment because I want to eventually compare to SemiclassicalOPs.jl.

There are a few limitations to fully exploring the limits of this approach as it stands:

1) Adaptive cholesky seems to currently not want to play well with the Clenshaw type, meaning that we can't just compute the weight multiplication matrix via the standard way of writing something like W = P \ (w.(x) .* P). I also cannot see an acceptably fast way to convert from Clenshaw to something more useful. Any ideas @dlfivefifty?
Nevermind, this actually works already, it just didn't know that W is symmetric. Easily fixed via Symmetric(W). That's great, I will update with more complicated examples soon.

  1. SymTridiagonal doesn't seem to behave like Symmetric either, meaning that I have to convert from J = jacobimatrix(P) to a symmetric Banded matrix at the moment in some places. This works fine as a proof of concept for the approach but we inevitably will want to not have that unnecessary conversion in there.

But the basic idea of the method seems to work. If we work on the above two points we can see where it starts to run into problems as well as see if it performs better or worse than Lanczos in accuracy / speed.

@codecov
Copy link

codecov bot commented Aug 13, 2021

Codecov Report

Merging #63 (d7b2997) into main (282129e) will increase coverage by 0.32%.
The diff coverage is 92.10%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #63      +/-   ##
==========================================
+ Coverage   89.11%   89.43%   +0.32%     
==========================================
  Files          13       14       +1     
  Lines        1552     1590      +38     
==========================================
+ Hits         1383     1422      +39     
+ Misses        169      168       -1     
Impacted Files Coverage Δ
src/ClassicalOrthogonalPolynomials.jl 90.37% <ø> (ø)
src/decompOPs.jl 92.10% <92.10%> (ø)
src/clenshaw.jl 98.52% <0.00%> (+1.96%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 282129e...d7b2997. Read the comment docs.

@TSGut
Copy link
Member Author

TSGut commented Aug 13, 2021

Updated now to work as follows

            P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])
            x = axes(P,1)
            J = jacobimatrix(P)
            wf(x) = x*(1-x^2)*exp(-x^2)
            # compute Jacobi matrix via cholesky
            Jchol = cholesky_jacobimatrix(wf, P)
            # compute Jacobi matrix via Lanczos
            Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])))
            # Comparison with Lanczos
            @test Jchol[1:500,1:500]  Jlanc[1:500,1:500]

and with arbitrary OP basis, at least arbitrary Jacobi basis. When P is not Legendre, W is assumed to be the weight modification operator rather than simply the weight multiplication operator, i.e. it is generally the (new weight)/(old weight) multiplication operator.

@dlfivefifty
Copy link
Member

Shouldn't this just replace lanczos?

@TSGut
Copy link
Member Author

TSGut commented Aug 14, 2021

I think it's too early to say? If it ends up being better, yes, but there's still a lot to figure out so I'm keeping them both around to compare.

@MikaelSlevinsky
Copy link
Member

If I understand correctly, the inverse Cholesky factor of the polynomial weight modifier is the connection coefficient matrix?

@TSGut
Copy link
Member Author

TSGut commented Aug 14, 2021

Yes, that's right @MikaelSlevinsky. There is also a slightly different version that would do this with a different decomposition but for now this is the one I'm going to mainly work on.

Via Clenshaw approach approximately-banded multiplication operators it also works for non-polynomial weight modification but I'm still in the process of exploring exactly what the pros and cons of this approach are.

@MikaelSlevinsky
Copy link
Member

I think this is really cool!

You might have to spell out the cons of this connection to linear algebra -- I only see pros. For example,

  • I just checked and you can also do rational modification as a product of one inverse Cholesky factor and another Cholesky factor. This is really great because rational gives much more approximation power for singularities, and it also avoids needing singular integrals (via second kind functions).
  • maybe an LDLT factorization can be used to connect to more formal orthogonal polynomials defined by a linear functional (that's not necessarily a positive-definite inner product). This is a bit speculative, but the factorization begs for the connection.
  • it's all fast because it's based on banded linear algebra.

@TSGut
Copy link
Member Author

TSGut commented Aug 14, 2021

Yes, it's really cool isn't it? I am very excited about this as an approach overall, I just don't want to promise too much before I've had the time to properly stress test it. We only very recently figured out this connection (as well as a QR analogue which I might implement at some point which in my tests so far is also performing quite well), following on from Sheehan's blogpost about the Lanczos approach a while back. There is some previous notice of this connection in the literature but it seems it never was used for anything and stayed a side-observation but it seems that decomposition methods can do a lot of good work for us.

From what I can tell it's more stable than Lanczos as long as both Lanczos and Cholesky approach are started from the same initial basis. It also seems that like Lanczos, the closer the initial weight is to the final one the better accuracy we get. I'd say that's all fairly expected.

I am working on the analysis part, this is sort of my in-practice sanity check until it's fully ready. It's can all be done adaptively and banded so ideally as Sheehan said this can replace Lanczos in most scenarios.

I'm very interested in your first point actually, maybe we should talk more about what you have in mind?

@MikaelSlevinsky
Copy link
Member

Hmm, I overlooked something and now it's not quite that simple. I'll describe the polynomial case then outline how to modify it for rational w(x).

Polynomial w(x). Let p be the quasi matrix of orthonormal polynomials, such as Legendre above. Let q be the quasi matrix orthonormal w.r.t. the modified weight. Then they are connected as q = p C. Your method works by the sequence of equalities

I = ∫ q' w q = ∫ C' p' w p C = ∫ C' p' p W C = C' ∫ p' p W C = C' W C.

It follows that if W = R'R, then we may take C = R^{-1}.

Rational w(x). Let w(x) = u(x)/v(x). Then,

C^{-1} V C = ∫ q' w q C^{-1} V C = ∫ q' w p V C  = ∫ q' w v p C = ∫ q' u p C = ∫ C' p' u p C = C' ∫ p' p U C = C' U C.

The relationship between the inverse connection coefficients and the symmetric and banded multiplication matrices U and V is then C^{-T} C^{-1} = UV^{-1}. This might be something that can be worked with.

(In both cases, the modified Jacobi matrix J is related to the one for p (call it X), by J = C^{-1} X C.)

@TSGut
Copy link
Member Author

TSGut commented Aug 14, 2021

I think I am already making use of that? The polynomial case as you describe it is more or less exactly how I first found this relationship and once one has that it's straightforward to generalise this for non-Legendre as well (although it's particularly pretty for Legendre). The case of rational weight is then somewhat analogous to the problem of starting with a non-Legendre basis.

Ideally the weights would cancel such that u(x)/v(x) is a polynomial, e.g. mapping between different Jacobi basis already works:

P = Normalized(Jacobi(1,1))
# modification by (1-x)^2 should map from Jacobi(1,1) to Jacobi(3,1)
wf(x) = (1-x)^2
# compute Jacobi(3,1) jacobimatrix via cholesky
Jchol = cholesky_jacobimatrix(wf, P)
# compare with canonical result
Q = Normalized(Jacobi(3,1))
J = jacobimatrix(Q)
@test J[1:500,1:500]  Jchol[1:500,1:500] 

and in those cases we can just take modifier w(x) as it is. If w(x)=u(x)/v(x) is not a polynomial, then we can still approximate it as a polynomial by means of the Clenshaw multiplication operator machinery in ClassicalOPs.jl. This will approximate the resulting dense operator as a banded operator and in fact I've already got this working as well. So take very non-polynomial weight and we can do

P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])
x = axes(P,1)
J = jacobimatrix(P)
wf(x) = exp(x^2)/(1.4-x)^2
# compute Jacobi matrix via cholesky
Jchol = cholesky_jacobimatrix(wf, P)
# compute Jacobi matrix via Lanczos
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])))
# Comparison with Lanczos
@test Jchol[1:500,1:500]  Jlanc[1:500,1:500]

So far so good, but this is of course a bad idea if the weight has singularities on the domain boundary as it will be terribly slow - for more well-behaved things it's perfectly fine and quite fast. This is why there is potential for use in semiclassical bases.

I've been wondering if we can do better for weights with singularities but it isn't obvious how to. Computing U and V, then computing the W operator as U/V isn't better (in fact it's arguably worse). There is also no obvious way at least as far as I know from the Cholesky decomposition of U and V to obtaining the Cholesky decomposition of U/V.

@dlfivefifty
Copy link
Member

FYI Normalized(legendre(0..1)) is equivalent to Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:])

@MikaelSlevinsky
Copy link
Member

I guess the rational case can be done in a few steps. Assume that w(x) = u(x)/v(x) and u and v are both expanded in the original (un-modified) OP basis, p.

  1. Find the intermediate (polynomially) modified basis by C_1 = inv(cholesky(U).U).
  2. Migrate the v(x) expansion in p to the intermediate modified basis by applying the connection coefficients C_1 to the expansion coefficients of v. Compute the intermediate modified Jacobi operator to be able to apply Clenshaw algorithm in the next step.
  3. Find the final (rational) modified basis by modifying the intermediate basis (which we know everything about given the Jacobi operator) by C_2 = cholesky(V).U. Notably, this V matrix is formed from v(x) expanded in the intermediate modified basis using the Clenshaw algorithm.

Is the full connection problem not just C_2*C_1, a product of (inverse) Cholesky factors?

If this works, I think it's better to do rational modifications like this rather than approximating the rational as a polynomial.

It would still be nice to do the full rational case in one sweep.

@MikaelSlevinsky
Copy link
Member

I guess solving V = CC' for C is like an "upper-lower" analogue of Cholesky.

@TSGut
Copy link
Member Author

TSGut commented Aug 15, 2021

No, not quite (if I understand you correctly) but I've been thinking about this for a bit now as well and I also think we can do it.

Start in the basis with u(x) as its weight (if it's not a classical one, then just move there first via Lanczos or Cholesky - I think you were also thinking along these lines?). Compute the V_u multiplication operator and its pseudo-Cholesky decomposition RR'. The above pseudo-Cholesky decomp is unfortunately not a Cholesky decomposition so we have to do this dance of stepping through two bases. (edited to make more sense of what I was trying to say)

@TSGut
Copy link
Member Author

TSGut commented Aug 15, 2021

Ah, there you go, you saw the issue already. Yes, that is entirely doable. A possible problem from the adaptive perspective is that I think the CC' decomposition doesn't go with the infinite matrices as well as Cholesky does. But in principle at least for finite chunks it should work for rational weights as we described.

@TSGut
Copy link
Member Author

TSGut commented Aug 15, 2021

@dlfivefifty any opinions on M = RR' decompositions with R upper triangular, so an analogue of Cholesky but with the upper and lower roles reversed? Am I right with the suspicion that for infinite arrays there is a problem of it wanting to start in the lower right end?

For finite matrices it's equivalent to Cholesky if we apply permutation matrices in the right places. Not really an option for infinite case.

If we can do this in a fast and stable way then rational polynomial weights are within reach with this approach. Basic examples already work for me.

@dlfivefifty
Copy link
Member

For Chebyshev they’ll be toeplitz so coming back from ∞ is Wiener hopf

legendre case needs some thought

@MikaelSlevinsky
Copy link
Member

Processing numerator and denominator modifications separately is sensitive to common-root problems, where neither multiplication operator is positive-definite.

That means either pre-processing to remove common roots or thinking of a combined modification.

@MikaelSlevinsky
Copy link
Member

The full rational case can also be done with an infinite QL decomposition. Suppose U and V are both expressed in the unmodified basis. Since they're multiplication operators, they (and their inverses) commute, so that C^{-t} C^{-1} = U V^{-1} = V^{-1} U.

  1. Let V = QL. It follows that L C^{-t} C^{-1} L^t = Q^t U L^t is both symmetric positive-definite and banded. SPD follows from the left-hand side, and it's banded from the right-hand side (Q's Householder vectors are short).
  2. Take the Cholesky decomposition of Q^t U L^t = K^t K. Then C^{-t} C^{-1} = L^{-1} K^t K L^{-t} so that C^{-1} = K L^{-t} or C = L^t K^{-1}.

The advantage in working with both U and V in the same step is that common-root problems would likely only cause numerical zeros in some outermost bands of some banded matrices. The tricky part is finding the QL decomposition. Maybe it can be done by doubling the dimensions until convergence sets in?

@TSGut
Copy link
Member Author

TSGut commented Aug 17, 2021

QL decompositions of infinite matrices are already implemented in InfiniteLinearAlgebra.jl for perturbations of Toeplitz operators, perhaps there is a way to make that work for asymptotically Toeplitz operators as well. Uniqueness of the decomposition may also be a concern. Just scaling it to convergence is probably an option as you say, at least my basic testing suggests that it would work, but it would be slow to use that for anything adaptive since the decomposition would have to be re-computed in full instead of extended. Might be worth the cost in the end for rational weights, almost certainly still better than approximating it as a polynomial, but I'll give it some thought if we can do more.

@TSGut
Copy link
Member Author

TSGut commented Aug 17, 2021

Since V is symmetric we can equivalently compute its RQ decomposition instead of QL without any extra costs, but the issues involved remain. There doesn't seem to be a way to do this without having to start in the lower right end, it's either a pseudo Cholesky decomp like UU' or an RQ/QL decomp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants