-
Notifications
You must be signed in to change notification settings - Fork 8
Support Jacobi matrix computation via Cholesky and QR #120
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
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
ccea7f9
Merge pull request #1 from JuliaApproximation/main
TSGut 21bd437
workaround for bug
TSGut ef8c485
Update README.md
TSGut ebd9e47
Merge remote-tracking branch 'origin/main' into main
TSGut 4310453
begin also implementing QR variant
TSGut b60c890
rm wrong type test
TSGut ad2ebdb
rename files
TSGut eb0fb68
more name changing
TSGut 2c7eeb8
minor changes
TSGut 74334d1
Merge branch 'main' into main
TSGut b8f8118
Merge pull request #7 from JuliaApproximation/main
TSGut 0c2db8d
minor changes to tests
TSGut 5a64f79
make some safety checks optional via bool
TSGut a8a747b
Merge branch 'main' into main
TSGut 10088b6
fix minor typo in main branch comments
TSGut c74ef5e
change entry computation + remove redundant field
TSGut d78f256
clarify a comment
TSGut d0cf141
add more tests
TSGut ce99a40
Merge remote-tracking branch 'origin/main' into main
TSGut 51cff6d
use known lengths instead of padding
TSGut b5d622d
remove redundant line in test
TSGut 7269e53
lots of small changes, see description
TSGut 6693844
minor type adjustments
TSGut f2c7d16
remove now unnecessary methods
TSGut 4496079
simplify resizing since we are using vectors now
TSGut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
""" | ||
cholesky_jacobimatrix(w, P) | ||
|
||
returns the Jacobi matrix `X` associated to a quasi-matrix of polynomials | ||
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P`. | ||
|
||
The resulting polynomials are orthonormal on the same domain as `P`. The supplied `P` must be normalized. Accepted inputs are `w` as a function or `W` as an infinite matrix representing multiplication with the function `w` on the basis `P`. | ||
|
||
An optional bool can be supplied, i.e. `cholesky_jacobimatrix(sqrtw, P, false)` to disable checks of symmetry for the weight multiplication matrix and orthonormality for the basis (use with caution). | ||
""" | ||
function cholesky_jacobimatrix(w::Function, P::OrthogonalPolynomial, checks::Bool = true) | ||
checks && !(P isa Normalized) && error("Polynomials must be orthonormal.") | ||
W = Symmetric(P \ (w.(axes(P,1)) .* P)) # Compute weight multiplication via Clenshaw | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bands = CholeskyJacobiBands(W, P) | ||
return SymTridiagonal(bands[1,:],bands[2,:]) | ||
end | ||
function cholesky_jacobimatrix(W::AbstractMatrix, P::OrthogonalPolynomial, checks::Bool = true) | ||
checks && !(P isa Normalized) && error("Polynomials must be orthonormal.") | ||
checks && !(W isa Symmetric) && error("Weight modification matrix must be symmetric.") | ||
bands = CholeskyJacobiBands(W, P) | ||
return SymTridiagonal(bands[1,:],bands[2,:]) | ||
end | ||
|
||
# The generated Jacobi operators are symmetric tridiagonal, so we store their data as two bands | ||
mutable struct CholeskyJacobiBands{T} <: AbstractCachedMatrix{T} | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data::Matrix{T} # store so-far computed block of entries | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
U::UpperTriangular # store upper triangular conversion matrix (needed to extend available entries) | ||
X::SymTridiagonal{T} # store the Jacobi matrix of the original basis (needed to extend available entries) | ||
datasize::Int # size of so-far computed block | ||
end | ||
|
||
# Computes the initial data for the Jacobi operator bands | ||
function CholeskyJacobiBands(W::Symmetric{T}, P::OrthogonalPolynomial) where T | ||
U = cholesky(W).U | ||
X = jacobimatrix(P) | ||
dat = zeros(T,2,10) | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
UX = U*X | ||
dat[1,1] = (UX * (U \ [1; zeros(∞)]))[1] | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dat[2,1] = (UX * (U \ [zeros(1); 1; zeros(∞)]))[1] | ||
@inbounds for k in 2:10 | ||
yk = view(UX,k,k-1:k+1) | ||
dat[1,k] = dot(yk[1:2], U[k-1:k,k-1:k] \ [0; 1]) | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dat[2,k] = dot(yk, U[k-1:k+1,k-1:k+1] \ [zeros(2); 1]) | ||
end | ||
return CholeskyJacobiBands{T}(dat, U, X, 10) | ||
end | ||
|
||
size(::CholeskyJacobiBands) = (2,ℵ₀) # Stored as two infinite bands | ||
|
||
# Resize and filling functions for cached implementation | ||
function resizedata!(K::CholeskyJacobiBands, nm::Integer) | ||
νμ = K.datasize | ||
if nm > νμ | ||
olddata = copy(K.data) | ||
K.data = similar(K.data, 2, nm) | ||
K.data[axes(olddata)...] = olddata | ||
inds = νμ:nm | ||
cache_filldata!(K, inds) | ||
K.datasize = nm | ||
end | ||
K | ||
end | ||
function cache_filldata!(J::CholeskyJacobiBands, inds::UnitRange{Int}) | ||
UX = J.U*J.X | ||
@inbounds for k in inds | ||
yk = view(UX,k,k-1:k+1) | ||
J.data[1,k] = dot(yk[1:2], J.U[k-1:k,k-1:k] \ [0; 1]) | ||
J.data[2,k] = dot(yk, J.U[k-1:k+1,k-1:k+1] \ [zeros(2); 1]) | ||
end | ||
end | ||
|
||
function getindex(K::CholeskyJacobiBands, k::Integer, j::Integer) | ||
resizedata!(K, max(k,j)) | ||
K.data[k, j] | ||
end | ||
function getindex(K::CholeskyJacobiBands, kr::Integer, jr::UnitRange{Int}) | ||
resizedata!(K, maximum(jr)) | ||
K.data[kr, jr] | ||
end | ||
function getindex(K::CholeskyJacobiBands, I::Vararg{Int,2}) | ||
resizedata!(K,maximum(I)) | ||
getindex(K.data,I[1],I[2]) | ||
end | ||
|
||
""" | ||
qr_jacobimatrix(sqrtw, P) | ||
|
||
returns the Jacobi matrix `X` associated to a quasi-matrix of polynomials | ||
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P`. | ||
|
||
The resulting polynomials are orthonormal on the same domain as `P`. The supplied `P` must be normalized. Accepted inputs for `sqrtw` are the square root of the weight modification as a function or `sqrtW` as an infinite matrix representing multiplication with the function `sqrt(w)` on the basis `P`. | ||
|
||
An optional bool can be supplied, i.e. `qr_jacobimatrix(sqrtw, P, false)` to disable checks of symmetry for the weight multiplication matrix and orthonormality for the basis (use with caution). | ||
""" | ||
function qr_jacobimatrix(sqrtw::Function, P::OrthogonalPolynomial, checks::Bool = true) | ||
checks && !(P isa Normalized) && error("Polynomials must be orthonormal.") | ||
sqrtW = (P \ (sqrtw.(axes(P,1)) .* P)) # Compute weight multiplication via Clenshaw | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bands = QRJacobiBands(sqrtW,P) | ||
K = SymTridiagonal(bands[1,:],bands[2,:]) | ||
return K | ||
end | ||
function qr_jacobimatrix(sqrtW::AbstractMatrix, P::OrthogonalPolynomial, checks::Bool = true) | ||
checks && !(P isa Normalized) && error("Polynomials must be orthonormal.") | ||
checks && !(sqrtW isa Symmetric) && error("Weight modification matrix must be symmetric.") | ||
bands = QRJacobiBands(sqrtW,P) | ||
K = SymTridiagonal(bands[1,:],bands[2,:]) | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return K | ||
end | ||
|
||
# The generated Jacobi operators are symmetric tridiagonal, so we store their data as two bands | ||
TSGut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mutable struct QRJacobiBands{T} <: AbstractCachedMatrix{T} | ||
data::Matrix{T} # store so-far computed block of entries | ||
U::UpperTriangular # store upper triangular conversion matrix (needed to extend available entries) | ||
X::SymTridiagonal{T} # store the Jacobi matrix of the original basis (needed to extend available entries) | ||
datasize::Int # size of so-far computed block | ||
end | ||
|
||
# Computes the initial data for the Jacobi operator bands | ||
function QRJacobiBands(sqrtW, P::OrthogonalPolynomial{T}) where T | ||
U = qr(sqrtW).R | ||
U = ApplyArray(*,Diagonal(sign.(view(U,band(0)))),U) | ||
X = jacobimatrix(P) | ||
UX = U*X | ||
dat = zeros(T,2,10) | ||
dat[1,1] = (UX * (U \ [1; zeros(∞)]))[1] | ||
dat[2,1] = (UX * (U \ [zeros(1); 1; zeros(∞)]))[1] | ||
@inbounds for k in 2:10 | ||
yk = view(UX,k,k-1:k+1) | ||
dat[1,k] = dot(yk[1:2], U[k-1:k,k-1:k] \ [0; 1]) | ||
dat[2,k] = dot(yk, U[k-1:k+1,k-1:k+1] \ [zeros(2); 1]) | ||
end | ||
return QRJacobiBands{T}(dat, UpperTriangular(U), X, 10) | ||
end | ||
|
||
size(::QRJacobiBands) = (2,ℵ₀) # Stored as two infinite bands | ||
|
||
# Resize and filling functions for cached implementation | ||
function resizedata!(K::QRJacobiBands, nm::Integer) | ||
νμ = K.datasize | ||
if nm > νμ | ||
olddata = copy(K.data) | ||
K.data = similar(K.data, 2, nm) | ||
K.data[axes(olddata)...] = olddata | ||
inds = νμ:nm | ||
cache_filldata!(K, inds) | ||
K.datasize = nm | ||
end | ||
K | ||
end | ||
function cache_filldata!(J::QRJacobiBands, inds::UnitRange{Int}) | ||
UX = J.U*J.X | ||
@inbounds for k in inds | ||
yk = view(UX,k,k-1:k+1) | ||
J.data[1,k] = dot(yk[1:2], J.U[k-1:k,k-1:k] \ [0; 1]) | ||
J.data[2,k] = dot(yk, J.U[k-1:k+1,k-1:k+1] \ [zeros(2); 1]) | ||
end | ||
end | ||
|
||
function getindex(K::QRJacobiBands, k::Integer, j::Integer) | ||
resizedata!(K, max(k,j)) | ||
K.data[k, j] | ||
end | ||
function getindex(K::QRJacobiBands, kr::Integer, jr::UnitRange{Int}) | ||
resizedata!(K, maximum(jr)) | ||
K.data[kr, jr] | ||
end | ||
function getindex(K::QRJacobiBands, I::Vararg{Int,2}) | ||
resizedata!(K,maximum(I)) | ||
getindex(K.data,I[1],I[2]) | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
using Test, ClassicalOrthogonalPolynomials, BandedMatrices, LinearAlgebra, LazyArrays, ContinuumArrays, LazyBandedMatrices, InfiniteLinearAlgebra | ||
import ClassicalOrthogonalPolynomials: CholeskyJacobiBands, cholesky_jacobimatrix, qr_jacobimatrix, QRJacobiBands | ||
import LazyArrays: AbstractCachedMatrix | ||
|
||
@testset "Cholesky Jacobi Matrix - Basic properties" begin | ||
# basis | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
# example weight | ||
w(x) = (1 - x^2) | ||
W = Symmetric(P \ (w.(x) .* P)) | ||
# banded cholesky for symmetric-tagged W | ||
@test cholesky(W).U isa UpperTriangular | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(w,P) | ||
Jcholalt = cholesky_jacobimatrix(W,P) | ||
@test Jchol[1:200,1:200] ≈ Jcholalt[1:200,1:200] | ||
# CholeskyJacobiBands object | ||
Cbands = CholeskyJacobiBands(W,P) | ||
@test Cbands isa CholeskyJacobiBands | ||
@test Cbands isa AbstractCachedMatrix | ||
@test getindex(Cbands,1,100) == getindex(Cbands,1,1:100)[100] | ||
@test getindex(Cbands,2,2) == getindex(Cbands,1:2,1:2)[2,2] | ||
end | ||
|
||
@testset "Comparison with Lanczos and Classical" begin | ||
@testset "Using Clenshaw for polynomial weights" begin | ||
@testset "w(x) = x^2*(1-x)" begin | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = x^2*(1-x) | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via classical recurrence | ||
Q = Normalized(Jacobi(1,2)[affine(0..1,Inclusion(-1..1)),:]) | ||
Jclass = jacobimatrix(Q) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
# Comparison with Classical | ||
@test Jchol[1:500,1:500] ≈ Jclass[1:500,1:500] | ||
end | ||
|
||
@testset "w(x) = (1-x^2)" begin | ||
P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:]) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = (1-x^2) | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
|
||
@testset "w(x) = (1-x^4)" begin | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = (1-x^4) | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
|
||
@testset "w(x) = (1.014-x^3)" begin | ||
P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:]) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = 1.014-x^4 | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
end | ||
|
||
@testset "Using Cholesky with exponential weights" begin | ||
@testset "w(x) = exp(x)" begin | ||
P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:]) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = exp(x) | ||
# compute Jacobi matrix via cholesky | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
|
||
@testset "w(x) = (1-x)*exp(x)" begin | ||
P = Normalized(Legendre()[affine(0..1,Inclusion(-1..1)),:]) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = (1-x)*exp(x) | ||
# 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] | ||
end | ||
|
||
@testset "w(x) = (1-x^2)*exp(x^2)" begin | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = (1-x)^2*exp(x^2) | ||
# compute Jacobi matrix via decomp | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
|
||
@testset "w(x) = x*(1-x^2)*exp(-x^2)" begin | ||
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(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
end | ||
end | ||
|
||
@testset "QR version" begin | ||
@testset "basic properties" begin | ||
# basis | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
# example weight | ||
sqrtw(x) = (1 - x) | ||
sqrtW = Symmetric(P \ (sqrtw.(x) .* P)) | ||
# bands test | ||
QRbands = QRJacobiBands(sqrtW,P) | ||
@test QRbands isa QRJacobiBands | ||
@test QRbands isa AbstractCachedMatrix | ||
@test getindex(QRbands,1,100) == getindex(QRbands,1,1:100)[100] | ||
@test getindex(QRbands,2,2) == getindex(QRbands,1:2,1:2)[2,2] | ||
end | ||
|
||
@testset "QR case, w(x) = (1-x)^2" begin | ||
P = Normalized(legendre(0..1)) | ||
x = axes(P,1) | ||
J = jacobimatrix(P) | ||
wf(x) = (1-x)^2 | ||
sqrtwf(x) = (1-x) | ||
# compute Jacobi matrix via decomp | ||
Jchol = cholesky_jacobimatrix(wf, P) | ||
Jqr = qr_jacobimatrix(sqrtwf, P) | ||
# use alternative inputs | ||
sqrtW = (P \ (sqrtwf.(x) .* P)) | ||
Jqralt = qr_jacobimatrix(sqrtW, P, false) | ||
# compute Jacobi matrix via Lanczos | ||
Jlanc = jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1)))) | ||
# Comparison with Lanczos | ||
@test Jchol[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
@test Jqr[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
@test Jqralt[1:500,1:500] ≈ Jlanc[1:500,1:500] | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to remove
::OrthogonalPolyomial
as not all orthogonal polynomials are<: OrthogonalPolynomial
, eg, any mapped polynomialsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a sensible replacement? I guess I can just hope the user only gives it sensible input? I can try to compute P's jacobi matrix and then catch an error to return something like "Failed to compute Jacobi matrix, check the supplied Polynomials" or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who cares if the user doesn't give sensible input? It will just error anyways so why even check??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And who's "the user"??? Me??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are the user (for now but others are using this package). 😄 It's nice to get sensible error messages but tbf something like "no method matching jacobimatrix(::Type)" will be fine even for those standards and that will be automatic