diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml index c743950..ed371b7 100644 --- a/.JuliaFormatter.toml +++ b/.JuliaFormatter.toml @@ -1 +1,2 @@ -style = "blue" \ No newline at end of file +style = "blue" +join_lines_based_on_source = true \ No newline at end of file diff --git a/Project.toml b/Project.toml index 26605fc..227f960 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Samuel Badr ", "Hiroshi Shinaoka @inbounds(U[k, i] * U[k, j]), 1:ii) + Hjj = sum(abs2, @view U[:, j]) offd += abs2(Hij) # diagonalize @@ -162,36 +156,27 @@ function jacobi_sweep!(U::AbstractMatrix, VT::AbstractMatrix) end """Singular value decomposition using Jacobi rotations.""" -function svd_jacobi!(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where T +function svd_jacobi!(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where {T} m, n = size(U) - if m < n - throw(ArgumentError("matrix must be 'tall'")) - end + m ≥ n || throw(ArgumentError("matrix must be 'tall'")) Base.require_one_based_indexing(U) # TODO VT = Matrix(one(T) * LinearAlgebra.I, n, n) Unorm = LinearAlgebra.norm(@view U[1:n, 1:n]) for _ in 1:maxiter offd = jacobi_sweep!(U, VT) - if offd < rtol * Unorm - break - end + offd < rtol * Unorm && break end - s = Vector{T}(undef, n) - @inbounds for i in 1:n - s[i] = LinearAlgebra.norm(@view U[:, i]) - # For some reason, U[:,i] ./= s[i] creates a copy - for j in axes(U, 1) - U[j, i] /= s[i] - end - end + s = LinearAlgebra.norm.(eachcol(U)) + U ./= transpose(s) return LinearAlgebra.SVD(U, s, VT) end """Singular value decomposition using Jacobi rotations.""" -svd_jacobi(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where T = - svd_jacobi!(copy(U); rtol=rtol, maxiter=maxiter) +function svd_jacobi(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where {T} + return svd_jacobi!(copy(U); rtol, maxiter) +end """ Truncated rank-revealing QR decomposition with full column pivoting. @@ -204,7 +189,7 @@ where `Q` is an `(m, k)` isometric matrix, `R` is a `(k, n)` upper triangular matrix, `piv` is a permutation vector, and `k` is chosen such that the relative tolerance `tol` is met in the equality above. """ -function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat +function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} # DGEQPF m, n = size(A) k = min(m, n) @@ -219,7 +204,7 @@ function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat sqrteps = sqrt(eps(T)) @inbounds for i in 1:k - pvt = argmax(@view pnorms[i:end]) + i - 1 + pvt = argmax(ii -> pnorms[ii], i:n) if i != pvt jpvt[i], jpvt[pvt] = jpvt[pvt], jpvt[i] xnorms[pvt] = xnorms[i] @@ -233,15 +218,16 @@ function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat tau_i = LinearAlgebra.reflector!(@view A[i:end, i]) taus[i] = tau_i LinearAlgebra.reflectorApply!( - (@view A[i:end, i]), tau_i, @view A[i:end, i+1:end]) + (@view A[i:end, i]), tau_i, @view A[i:end, (i + 1):end] + ) # Lapack Working Note 176. - for j in i+1:n - temp = abs(A[i,j]) / pnorms[j] + for j in (i + 1):n + temp = abs(A[i, j]) / pnorms[j] temp = max(zero(T), (one(T) + temp) * (one(T) - temp)) temp2 = temp * abs2(pnorms[j] / xnorms[j]) if temp2 < sqrteps - recomputed = LinearAlgebra.norm(@view A[i+1:end, j]) + recomputed = LinearAlgebra.norm(@view A[(i + 1):end, j]) pnorms[j] = recomputed xnorms[j] = recomputed else @@ -258,19 +244,16 @@ function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat break end end - return LinearAlgebra.QRPivoted{T, typeof(A)}(A, taus, jpvt), k + return LinearAlgebra.QRPivoted{T,typeof(A)}(A, taus, jpvt), k end """Truncated rank-revealing QR decomposition with full column pivoting.""" -rrqr(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat = - rrqr!(copy(A); rtol=rtol) +rrqr(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} = rrqr!(copy(A); rtol) """Truncate RRQR result low-rank""" -function truncate_qr_result(qr::LinearAlgebra.QRPivoted{T}, k::Integer) where T +function truncate_qr_result(qr::LinearAlgebra.QRPivoted{T}, k::Integer) where {T} m, n = size(qr) - if k < 0 || k > min(m, n) - throw(ArgumentError("Invalid rank")) - end + 0 ≤ k ≤ min(m, n) || throw(DomainError(k, "Invalid rank, must be in [0, $(min(m, n))]")) Qfull = LinearAlgebra.QRPackedQ((@view qr.factors[:, 1:k]), qr.τ[1:k]) Q = LinearAlgebra.lmul!(Qfull, Matrix{T}(LinearAlgebra.I, m, k)) @@ -290,13 +273,13 @@ matrix with orthogonal rows and `s` are the singular values, a set of `k` nonnegative numbers in non-ascending order. The SVD is truncated in the sense that singular values below `tol` are discarded. """ -function tsvd!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat +function tsvd!(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} # Perform RRQR of the m x n matrix at a cost of O(m*n*k), where k is # the QR rank (a mild upper bound for the true rank) - A_qr, k = rrqr!(A, rtol=rtol) + A_qr, k = rrqr!(A; rtol) Q, R = truncate_qr_result(A_qr, k) - # RRQR is an excellent preconditioner for Jacobi. One should then perform + # RRQR is an excellent preconditioner for Jacobi. One should then perform # Jacobi on RT RT_svd = svd_jacobi(R') @@ -308,7 +291,6 @@ function tsvd!(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat end """Truncated singular value decomposition.""" -tsvd(A::AbstractMatrix{T}; rtol=eps(T)) where T <: AbstractFloat = - tsvd!(copy(A); rtol=rtol) +tsvd(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} = tsvd!(copy(A); rtol) end # module _LinAlg diff --git a/src/augment.jl b/src/augment.jl index b0bcf04..a711df5 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -18,7 +18,7 @@ struct LegendreBasis{T<:AbstractFloat} <: AbstractBasis statistics::Statistics β::Float64 cl::Vector{T} - u::PiecewiseLegendrePolyArray{T} + u::PiecewiseLegendrePolyVector{T} uhat::PiecewiseLegendreFTArray{T} end @@ -38,12 +38,12 @@ function LegendreBasis( for l in 1:size data[l, 1, l] = sqrt(((l - 1) + 0.5) / beta) * cl[l] end - u = PiecewiseLegendrePolyArray(data, knots; symm) + u = PiecewiseLegendrePolyVector(data, knots; symm) # uhat - uhat_base = PiecewiseLegendrePolyArray(sqrt(beta) .* data, Float64[-1, 1]; symm) + uhat_base = PiecewiseLegendrePolyVector(sqrt(beta) .* data, Float64[-1, 1]; symm) even_odd = Dict(fermion => :odd, boson => :even)[statistics] - uhat = hat.(uhat_base, even_odd, 0:(size - 1)) + uhat = hat.(uhat_base, even_odd) return LegendreBasis(statistics, beta, cl, u, uhat) end @@ -60,7 +60,7 @@ iswellconditioned(basis::LegendreBasis) = true function default_tau_sampling_points(basis::LegendreBasis) x = gauss(length(basis.u))[1] - return (beta(basis) / 2) .* (x .+ 1) + return (getbeta(basis) / 2) .* (x .+ 1) end struct _ConstTerm{T<:Number} diff --git a/src/basis.jl b/src/basis.jl index 2e3131a..c48b0d9 100644 --- a/src/basis.jl +++ b/src/basis.jl @@ -1,7 +1,7 @@ abstract type AbstractBasis end Base.size(basis::AbstractBasis) = size(basis.u) -beta(basis::AbstractBasis) = basis.β +getbeta(basis::AbstractBasis) = basis.β statistics(basis::AbstractBasis) = basis.statistics """ @@ -21,7 +21,7 @@ kernel then only depends on a cutoff parameter `Λ = β * ωmax`. # Examples The following example code assumes the spectral function is a single -pole at `x = 0.2`. We first compute an IR basis suitable for fermions and `β*W <= 42`. Then we get G(iw) on the first few Matsubara frequencies: +pole at `x = 0.2`. We first compute an IR basis suitable for fermions and `β*W ≤ 42`. Then we get G(iw) on the first few Matsubara frequencies: ```julia-repl julia> using SparseIR @@ -34,7 +34,7 @@ julia> giw = transpose(basis.uhat([1, 3, 5, 7])) * gl ``` # Fields -- `u::PiecewiseLegendrePolyArray`: Set of IR basis functions on the reduced imaginary time (`x`) axis. These functions are stored as piecewise Legendre polynomials. +- `u::PiecewiseLegendrePolyVector`: Set of IR basis functions on the reduced imaginary time (`x`) axis. These functions are stored as piecewise Legendre polynomials. To obtain the value of all basis functions at a point or a array of points `x`, you can call the function `u(x)`. To obtain a single @@ -51,7 +51,7 @@ These objects are stored as a set of Bessel functions. - `s`: Vector of singular values of the continuation kernel -- `v::PiecewiseLegendrePolyArray`: Set of IR basis functions on the reduced real frequency (`y`) axis. +- `v::PiecewiseLegendrePolyVector`: Set of IR basis functions on the reduced real frequency (`y`) axis. These functions are stored as piecewise Legendre polynomials. To obtain the value of all basis functions at a point or a array of @@ -62,10 +62,10 @@ See also [`FiniteTempBasis`](@ref) for a basis directly in time/frequency. """ struct DimensionlessBasis{K<:AbstractKernel,T<:AbstractFloat} <: AbstractBasis kernel::K - u::PiecewiseLegendrePolyArray{T} + u::PiecewiseLegendrePolyVector{T} uhat::PiecewiseLegendreFTArray{T} s::Vector{T} - v::PiecewiseLegendrePolyArray{T} + v::PiecewiseLegendrePolyVector{T} sampling_points_v::Vector{T} statistics::Statistics end @@ -99,7 +99,7 @@ function DimensionlessBasis( # so for significantly larger frequencies we use the asymptotics, # since it has lower relative error. even_odd = Dict(fermion => :odd, boson => :even)[statistics] - û = hat.(u, even_odd, 0:(length(u) - 1); n_asymp=conv_radius(kernel)) + û = hat.(u, even_odd; n_asymp=conv_radius(kernel)) rts = roots(last(v)) sampling_points_v = [v.xmin; (rts[begin:(end - 1)] .+ rts[(begin + 1):end]) / 2; v.xmax] return DimensionlessBasis(kernel, u, û, s, v, sampling_points_v, statistics) @@ -146,7 +146,7 @@ julia> giw = transpose(basis.uhat([1, 3, 5, 7])) * gl ``` # Fields -- `u::PiecewiseLegendrePolyArray`: +- `u::PiecewiseLegendrePolyVector`: Set of IR basis functions on the imaginary time (`tau`) axis. These functions are stored as piecewise Legendre polynomials. @@ -176,11 +176,13 @@ julia> giw = transpose(basis.uhat([1, 3, 5, 7])) * gl """ struct FiniteTempBasis{K,T} <: AbstractBasis kernel::K - sve_result::Tuple{PiecewiseLegendrePolyArray{T},Vector{T},PiecewiseLegendrePolyArray{T}} + sve_result::Tuple{ + PiecewiseLegendrePolyVector{T},Vector{T},PiecewiseLegendrePolyVector{T} + } statistics::Statistics β::T - u::PiecewiseLegendrePolyArray{T} - v::PiecewiseLegendrePolyArray{T} + u::PiecewiseLegendrePolyVector{T} + v::PiecewiseLegendrePolyVector{T} s::Vector{T} uhat::PiecewiseLegendreFTArray{T} end @@ -188,7 +190,7 @@ end const _DEFAULT_FINITE_TEMP_BASIS = FiniteTempBasis{LogisticKernel{Float64},Float64} function Base.show(io::IO, a::FiniteTempBasis) - return print(io, "FiniteTempBasis($(statistics(a)), $(beta(a)), $(wmax(a)))") + return print(io, "FiniteTempBasis($(statistics(a)), $(getbeta(a)), $(getwmax(a)))") end """ @@ -198,19 +200,19 @@ Construct a finite temperature basis suitable for the given `statistics` and cut """ function FiniteTempBasis( statistics::Statistics, - β::T, + β, wmax, ε=nothing; kernel=LogisticKernel(β * wmax), sve_result=compute_sve(kernel; ε), -) where {T} +) β > 0 || throw(DomainError(β, "Inverse temperature β must be positive")) wmax ≥ 0 || throw(DomainError(wmax, "Frequency cutoff wmax must be non-negative")) - if isnothing(ε) && isnothing(sve_result) && !_HAVE_XPREC - @warn """No extended precision is being used. - Expect single precision (1.5e-8) only as both cutoff - and accuracy of the basis functions.""" - end + # if isnothing(ε) && isnothing(sve_result) && !_HAVE_XPREC + # @warn """No extended precision is being used. + # Expect single precision (1.5e-8) only as both cutoff + # and accuracy of the basis functions.""" + # end u, s, v = sve_result size(u) == size(s) == size(v) || throw(DimensionMismatch("Mismatched shapes in SVE")) @@ -219,8 +221,10 @@ function FiniteTempBasis( # knots according to: tau = beta/2 * (x + 1), w = wmax * y. Scaling # the data is not necessary as the normalization is inferred. wmax = kernel.Λ / β - u_ = PiecewiseLegendrePolyArray(u, β / 2 * (u.knots .+ 1); Δx=β / 2 * u.Δx, symm=u.symm) - v_ = PiecewiseLegendrePolyArray(v, wmax * v.knots; Δx=wmax * v.Δx, symm=v.symm) + u_ = PiecewiseLegendrePolyVector( + u, β / 2 * (u.knots .+ 1); Δx=β / 2 * u.Δx, symm=u.symm + ) + v_ = PiecewiseLegendrePolyVector(v, wmax * v.knots; Δx=wmax * v.Δx, symm=v.symm) # The singular values are scaled to match the change of variables, with # the additional complexity that the kernel may have an additional @@ -234,9 +238,9 @@ function FiniteTempBasis( conv_radius = 40 * kernel.Λ even_odd = Dict(fermion => :odd, boson => :even)[statistics] - û = hat.(û_base, even_odd, 0:(length(u) - 1); n_asymp=conv_radius) + û = hat.(û_base, even_odd; n_asymp=conv_radius) - return FiniteTempBasis(kernel, sve_result, statistics, β, u_, v_, s_, û) + return FiniteTempBasis(kernel, sve_result, statistics, float(β), u_, v_, s_, û) end Base.firstindex(::AbstractBasis) = 1 @@ -254,16 +258,16 @@ function Base.getindex(basis::FiniteTempBasis, i) u, s, v = basis.sve_result sve_result = u[i], s[i], v[i] return FiniteTempBasis( - basis.statistics, beta(basis), wmax(basis); kernel=basis.kernel, sve_result + basis.statistics, getbeta(basis), getwmax(basis); kernel=basis.kernel, sve_result ) end """ - wmax(basis::FiniteTempBasis) + getwmax(basis::FiniteTempBasis) Real frequency cutoff. """ -wmax(basis::FiniteTempBasis) = basis.kernel.Λ / beta(basis) +getwmax(basis::FiniteTempBasis) = basis.kernel.Λ / getbeta(basis) """ finite_temp_bases(β, wmax, ε, sve_result=compute_sve(LogisticKernel(β * wmax); ε)) diff --git a/src/basis_set.jl b/src/basis_set.jl index c95a48c..395cec8 100644 --- a/src/basis_set.jl +++ b/src/basis_set.jl @@ -47,27 +47,28 @@ function FiniteTempBasisSet(beta, wmax, eps; sve_result=nothing) end return FiniteTempBasisSet( - basis_f, - basis_b, - TauSampling(basis_f), - TauSampling(basis_b), - MatsubaraSampling(basis_f), - MatsubaraSampling(basis_b), + basis_f, basis_b, + TauSampling(basis_f), TauSampling(basis_b), + MatsubaraSampling(basis_f), MatsubaraSampling(basis_b), ) end -beta(bset::FiniteTempBasisSet) = beta(bset.basis_f) -wmax(bset::FiniteTempBasisSet) = wmax(bset.basis_f) +getbeta(bset::FiniteTempBasisSet) = getbeta(bset.basis_f) +getwmax(bset::FiniteTempBasisSet) = getwmax(bset.basis_f) function Base.getproperty(bset::FiniteTempBasisSet, d::Symbol) if d === :tau - return getfield(bset, :smpl_tau_f).sampling_points + # return getfield(bset, :smpl_tau_f).sampling_points + return bset.smpl_tau_f.sampling_points elseif d === :wn_f - return getfield(bset, :smpl_wn_f).sampling_points + # return getfield(bset, :smpl_wn_f).sampling_points + return bset.smpl_wn_f.sampling_points elseif d === :wn_b - return getfield(bset, :smpl_wn_b).sampling_points + # return getfield(bset, :smpl_wn_b).sampling_points + return bset.smpl_wn_b.sampling_points elseif d === :sve_result - return getfield(bset, :basis_f).sve_result + # return getfield(bset, :basis_f).sve_result + return bset.basis_f.sve_result else return getfield(bset, d) end @@ -78,5 +79,5 @@ function Base.propertynames(::FiniteTempBasisSet, private::Bool=false) end function Base.show(io::IO, b::FiniteTempBasisSet) - return print(io, "FiniteTempBasisSet: beta=$(beta(b)), wmax=$(wmax(b))") + return print(io, "FiniteTempBasisSet: beta=$(getbeta(b)), wmax=$(getwmax(b))") end diff --git a/src/composite.jl b/src/composite.jl index b391f43..096addb 100644 --- a/src/composite.jl +++ b/src/composite.jl @@ -54,7 +54,7 @@ function CompositeBasis(bases::Vector{AbstractBasis}) u = CompositeBasisFunction([b.u for b in bases]) v = _collect_polys(CompositeBasisFunction, [b.v for b in bases]) uhat = _collect_polys(CompositeBasisFunctionFT, [b.uhat for b in bases]) - return CompositeBasis(beta(bases[1]), bases, u, v, uhat) + return CompositeBasis(getbeta(bases[1]), bases, u, v, uhat) end function default_tau_sampling_points(basis::CompositeBasis) diff --git a/src/kernel.jl b/src/kernel.jl index fbfa28f..f00988f 100644 --- a/src/kernel.jl +++ b/src/kernel.jl @@ -250,28 +250,9 @@ function segments_y(hints::SVEHintsLogistic) nzeros = max(round(Int, 20 * log10(hints.kernel.Λ)), 2) # Zeros around -1 and 1 are distributed asymptotically identically - leading_diffs = [ - 0.01523, - 0.03314, - 0.04848, - 0.05987, - 0.06703, - 0.07028, - 0.07030, - 0.06791, - 0.06391, - 0.05896, - 0.05358, - 0.04814, - 0.04288, - 0.03795, - 0.03342, - 0.02932, - 0.02565, - 0.02239, - 0.01951, - 0.01699, - ][begin:min(nzeros, 20)] + leading_diffs = [0.01523, 0.03314, 0.04848, 0.05987, 0.06703, 0.07028, 0.07030, 0.06791, + 0.06391, 0.05896, 0.05358, 0.04814, 0.04288, 0.03795, 0.03342, 0.02932, 0.02565, + 0.02239, 0.01951, 0.01699][begin:min(nzeros, 20)] diffs = [leading_diffs; 0.25 ./ exp.(0.141 * (20:(nzeros - 1)))] diff --git a/src/poly.jl b/src/poly.jl index c10410e..8652449 100644 --- a/src/poly.jl +++ b/src/poly.jl @@ -17,59 +17,40 @@ struct PiecewiseLegendrePoly{T} <: Function Δx::Vector{T} data::Matrix{T} symm::Int + l::Int xm::Vector{T} inv_xs::Vector{T} norm::Vector{T} function PiecewiseLegendrePoly( - nsegments, polyorder, xmin, xmax, knots, Δx, data, symm, xm, inv_xs, norm + nsegments, polyorder, xmin, xmax, knots, Δx, data, symm, l, xm, inv_xs, norm ) !any(isnan, data) || error("data contains NaN") size(knots) == (nsegments + 1,) || error("Invalid knots array") issorted(knots) || error("knots must be monotonically increasing") Δx ≈ diff(knots) || error("Δx must work with knots") return new{eltype(knots)}( - nsegments, polyorder, xmin, xmax, knots, Δx, data, symm, xm, inv_xs, norm + nsegments, polyorder, xmin, xmax, knots, Δx, data, symm, l, xm, inv_xs, norm ) end end function PiecewiseLegendrePoly(data, p::PiecewiseLegendrePoly; symm=0) - return PiecewiseLegendrePoly( - p.nsegments, - p.polyorder, - p.xmin, - p.xmax, - p.knots, - p.Δx, - data, - symm, - p.xm, - p.inv_xs, - p.norm, - ) + return PiecewiseLegendrePoly(p.nsegments, p.polyorder, p.xmin, p.xmax, p.knots, p.Δx, + data, symm, p.l, p.xm, p.inv_xs, p.norm) end -function PiecewiseLegendrePoly(data::Matrix, knots::Vector; Δx=diff(knots), symm=0) +function PiecewiseLegendrePoly( + data::Matrix, knots::Vector, l::Integer; Δx=diff(knots), symm=0 +) polyorder, nsegments = size(data) xm = @views (knots[1:(end - 1)] + knots[2:end]) / 2 inv_xs = 2 ./ Δx norm = sqrt.(inv_xs) - return PiecewiseLegendrePoly( - nsegments, - polyorder, - first(knots), - last(knots), - knots, - Δx, - data, - symm, - xm, - inv_xs, - norm, - ) + return PiecewiseLegendrePoly(nsegments, polyorder, first(knots), last(knots), knots, + Δx, data, symm, l, xm, inv_xs, norm) end Base.size(::PiecewiseLegendrePoly) = () @@ -103,7 +84,9 @@ using adaptive Gauss-Legendre quadrature. function overlap( poly::PiecewiseLegendrePoly{T}, f; rtol=eps(T), return_error=false, maxevals=10^4 ) where {T} - int_result, int_error = quadgk(x -> poly(x) * f(x), poly.knots...; rtol, order=10, maxevals) + int_result, int_error = quadgk( + x -> poly(x) * f(x), poly.knots...; rtol, order=10, maxevals + ) if return_error return int_result, int_error else @@ -129,30 +112,29 @@ end Find all roots of the piecewise polynomial `poly`. """ -function roots(poly::PiecewiseLegendrePoly{T}; tol=1e-11) where {T} +function roots(poly::PiecewiseLegendrePoly{T}; tol=1e-10) where {T} m = (poly.xmin + poly.xmax) / 2 xmin = abs(poly.symm) == 1 ? m : poly.xmin # Exploit symmetry. xmax = poly.xmax rts_rootobjects = roots_irf(poly, Interval(xmin, xmax), Newton, tol) - filter!(isunique, rts_rootobjects) - rts = map(mid ∘ interval, rts_rootobjects) + rts = map(mid ∘ interval, Iterators.filter(isunique, rts_rootobjects)) if abs(poly.symm) == 1 - # Reflect roots about the midpoint m. + # Reflect roots about the midpoint m append!(rts, 2m .- rts) - # If the polynomial is antisymmetric, it has an additional root at m. + # If the polynomial is antisymmetric, it has an additional root at m poly.symm == -1 && push!(rts, m) end sort!(rts) - # TODO: Maybe there's a better way than this. - duplicates = Int[] - for i in 2:length(rts) - isapprox(rts[i], rts[i - 1]; atol=10tol, rtol=0) && push!(duplicates, i) - end - deleteat!(rts, duplicates) - !isempty(duplicates) && @warn "Duplicate roots found, consolidating them..." + # Remove duplicates + # duplicates = Int[] + # for i in 2:length(rts) + # isapprox(rts[i], rts[i - 1]; atol=10tol, rtol=0) && push!(duplicates, i) + # end + # !isempty(duplicates) && @info "Duplicate roots found, consolidating them..." + # deleteat!(rts, duplicates) return rts end @@ -179,59 +161,64 @@ function _split(poly, x::Number) end function scale(poly::PiecewiseLegendrePoly, factor) - return PiecewiseLegendrePoly(poly.data * factor, poly.knots; Δx=poly.Δx, symm=poly.symm) + return PiecewiseLegendrePoly( + poly.data * factor, poly.knots, poly.l; Δx=poly.Δx, symm=poly.symm + ) end ########################### -## PiecewiseLegendrePolyArray ## +## PiecewiseLegendrePolyVector ## ########################### """ - PiecewiseLegendrePolyArray{T, N} + PiecewiseLegendrePolyVector{T} -Alias for `Array{PiecewiseLegendrePoly{T}, N}`. +Alias for `Vector{PiecewiseLegendrePoly{T}}`. """ -const PiecewiseLegendrePolyArray{T,N} = Array{PiecewiseLegendrePoly{T},N} +const PiecewiseLegendrePolyVector{T} = Vector{PiecewiseLegendrePoly{T}} -# TODO: simplify constructors -function PiecewiseLegendrePolyArray( - data::Array{T,N}, knots::Vector{T}; symm=zeros(Int, last(size(data))) -) where {T,N} - polys = PiecewiseLegendrePolyArray{T,N - 2}(undef, size(data)[3:end]...) +function PiecewiseLegendrePolyVector( + data::Array{T,3}, knots::Vector{T}; symm=zeros(Int, size(data, 3)) +) where {T} + polys = PiecewiseLegendrePolyVector{T}(undef, size(data, 3)) for i in eachindex(polys) - polys[i] = PiecewiseLegendrePoly(data[:, :, i], knots; symm=symm[i]) + polys[i] = PiecewiseLegendrePoly(data[:, :, i], knots, i - 1; symm=symm[i]) end return polys end -function PiecewiseLegendrePolyArray( - polys::PiecewiseLegendrePolyArray, knots; Δx=diff(knots), symm=0 +function PiecewiseLegendrePolyVector( + polys::PiecewiseLegendrePolyVector, knots; Δx=diff(knots), symm=0 ) - size(polys) == size(symm) || + length(polys) == length(symm) || throw(DimensionMismatch("Sizes of polys and symm don't match")) + polys_new = similar(polys) for i in eachindex(polys) - polys_new[i] = PiecewiseLegendrePoly(polys[i].data, knots; Δx, symm=symm[i]) + polys_new[i] = PiecewiseLegendrePoly( + polys[i].data, knots, polys[i].l; Δx, symm=symm[i] + ) end return polys_new end -function PiecewiseLegendrePolyArray(data, polys::PiecewiseLegendrePolyArray) - size(data)[3:end] == size(polys) || +function PiecewiseLegendrePolyVector(data, polys::PiecewiseLegendrePolyVector) + size(data, 3) == length(polys) || throw(DimensionMismatch("Sizes of data and polys don't match")) - polys = similar(polys) + + polys_new = copy(polys) for i in eachindex(polys) - polys[i] = PiecewiseLegendrePoly(data[:, :, i], polys.knots; symm=polys.symm[i]) + polys_new[i].data .= data[:, :, i] end - return polys + return polys_new end -(polys::PiecewiseLegendrePolyArray)(x) = map(poly -> poly(x), polys) -function (polys::PiecewiseLegendrePolyArray)(x::AbstractArray) +(polys::PiecewiseLegendrePolyVector)(x) = map(poly -> poly(x), polys) +function (polys::PiecewiseLegendrePolyVector)(x::AbstractArray) return reshape(reduce(vcat, polys.(x)), (size(polys)..., size(x)...)) end -function Base.getproperty(polys::PiecewiseLegendrePolyArray, sym::Symbol) +function Base.getproperty(polys::PiecewiseLegendrePolyVector, sym::Symbol) if sym ∈ (:xmin, :xmax, :knots, :Δx, :polyorder, :nsegments, :xm, :inv_xs, :norm) return getproperty(first(polys), sym) elseif sym == :symm @@ -245,7 +232,7 @@ end # Backward compatibility function overlap( - polys::PiecewiseLegendrePolyArray{T}, f; rtol=eps(T), return_error=false + polys::PiecewiseLegendrePolyVector{T}, f; rtol=eps(T), return_error=false ) where {T} return overlap.(polys, f; rtol, return_error) end @@ -300,10 +287,10 @@ function Base.show(io::IO, p::PiecewiseLegendreFT) return print(io, "$(typeof(p))") end -function PiecewiseLegendreFT(poly, freq=:even, n_asymp=Inf, l=0) +function PiecewiseLegendreFT(poly, freq=:even, n_asymp=Inf) (poly.xmin, poly.xmax) == (-1, 1) || error("Only interval [-1, 1] is supported") ζ = Dict(:even => 0, :odd => 1)[freq] - model = power_model(freq, poly, l) + model = power_model(freq, poly) return PiecewiseLegendreFT(poly, freq, ζ, float(n_asymp), model) end @@ -359,8 +346,8 @@ end Base.firstindex(::PiecewiseLegendreFT) = 1 -function hat(poly::PiecewiseLegendrePoly, freq, l; n_asymp=Inf) - return PiecewiseLegendreFT(poly, freq, n_asymp, l) +function hat(poly::PiecewiseLegendrePoly, freq; n_asymp=Inf) + return PiecewiseLegendreFT(poly, freq, n_asymp) end """ @@ -465,9 +452,9 @@ function power_moments(stat, deriv_x1, l) return -statsign / √2 * coeff_lm end -function power_model(stat, poly, l) +function power_model(stat, poly) deriv_x1 = derivs(poly, 1) - moments = power_moments(stat, deriv_x1, l) + moments = power_moments(stat, deriv_x1, poly.l) return PowerModel(moments) end @@ -503,7 +490,7 @@ function _compute_unl_inner(poly::PiecewiseLegendrePoly, wn) t_pin = _Pqn(poly, wn) return dot(poly.data, transpose(t_pin)) end -function _compute_unl_inner(polys::PiecewiseLegendrePolyArray, wn) +function _compute_unl_inner(polys::PiecewiseLegendrePolyVector, wn) t_pin = _Pqn(polys, wn) return map(p -> dot(p.data, transpose(t_pin)), polys) end diff --git a/src/spr.jl b/src/spr.jl index 5431935..779dd98 100644 --- a/src/spr.jl +++ b/src/spr.jl @@ -4,7 +4,7 @@ struct MatsubaraPoleBasis <: AbstractBasis end function (basis::MatsubaraPoleBasis)(n::Vector{T}) where {T<:Integer} - iv = (im * π / beta(basis)) .* n + iv = (im * π / getbeta(basis)) .* n return 1 ./ (transpose(iv) .- basis.poles) end @@ -15,19 +15,19 @@ struct TauPoleBasis <: AbstractBasis wmax::Float64 end -wmax(basis::TauPoleBasis) = basis.wmax +getwmax(basis::TauPoleBasis) = basis.wmax function TauPoleBasis(beta::Real, statistics::Statistics, poles::Vector{<:AbstractFloat}) return TauPoleBasis(beta, poles, statistics, maximum(abs, poles)) end function (basis::TauPoleBasis)(tau::Vector{<:AbstractFloat}) - all(τ -> 0 ≤ τ ≤ beta(basis), tau) || + all(τ -> 0 ≤ τ ≤ getbeta(basis), tau) || throw(DomainError(tau, "tau must be in [0, beta]!")) - x = (2 / beta(basis)) .* tau .- 1 - y = basis.poles ./ wmax(basis) - Λ = beta(basis) * wmax(basis) + x = (2 / getbeta(basis)) .* tau .- 1 + y = basis.poles ./ getwmax(basis) + Λ = getbeta(basis) * getwmax(basis) if basis.statistics == fermion res = -LogisticKernel(Λ).(x, transpose(y)) else @@ -53,16 +53,16 @@ end function SparsePoleRepresentation( basis::AbstractBasis, poles=default_omega_sampling_points(basis) ) - y_sampling_points = poles ./ wmax(basis) - u = TauPoleBasis(beta(basis), basis.statistics, poles) - uhat = MatsubaraPoleBasis(beta(basis), poles) + y_sampling_points = poles ./ getwmax(basis) + u = TauPoleBasis(getbeta(basis), basis.statistics, poles) + uhat = MatsubaraPoleBasis(getbeta(basis), poles) weight = weight_func(basis.kernel, basis.statistics)(y_sampling_points) fitmat = -basis.s .* basis.v(poles) .* transpose(weight) matrix = svd(fitmat) return SparsePoleRepresentation(basis, poles, u, uhat, basis.statistics, fitmat, matrix) end -beta(obj::SparsePoleRepresentation) = beta(obj.basis) +getbeta(obj::SparsePoleRepresentation) = getbeta(obj.basis) function Base.getproperty(obj::SparsePoleRepresentation, d::Symbol) if d === :v diff --git a/src/svd.jl b/src/svd.jl index 500f938..c97b7a8 100644 --- a/src/svd.jl +++ b/src/svd.jl @@ -1,12 +1,14 @@ -const _T_MAX = Float64 +const _T_MAX = Double64 const _EPS_MAX = eps(_T_MAX) -function compute_svd(a_matrix::AbstractMatrix{_T_MAX}; n_sv_hint=nothing, strategy=:fast) +function compute_svd(a_matrix::AbstractMatrix; n_sv_hint=nothing, strategy=:fast) m, n = size(a_matrix) isnothing(n_sv_hint) && (n_sv_hint = min(m, n)) n_sv_hint = min(m, n, n_sv_hint) - if strategy == :fast + if eltype(a_matrix) == _T_MAX + u, s, v = tsvd(a_matrix) + elseif strategy == :fast u, s, v = psvd(a_matrix; rank=n_sv_hint, rtol=0.0) elseif strategy == :default u, s, v = svd(a_matrix) diff --git a/src/sve.jl b/src/sve.jl index 76544b9..9afe40e 100644 --- a/src/sve.jl +++ b/src/sve.jl @@ -1,4 +1,4 @@ -const _HAVE_XPREC = false # TODO: +# const _HAVE_XPREC = false # TODO: abstract type AbstractSVE end @@ -8,7 +8,7 @@ abstract type AbstractSVE end SVE to SVD translation by sampling technique [1]. Maps the singular value expansion (SVE) of a kernel `kernel` onto the singular -value decomposition of a matrix `A`. This is achieved by chosing two +value decomposition of a matrix `A`. This is achieved by choosing two sets of Gauss quadrature rules: `(x, wx)` and `(y, wy)` and approximating the integrals in the SVE equations by finite sums. This implies that the singular values of the SVE are well-approximated by the @@ -24,19 +24,19 @@ be reconstructed from the singular vectors `u` and `v` as follows: [1] P. Hansen, Discrete Inverse Problems, Ch. 3.1 """ -struct SamplingSVE{K<:AbstractKernel} <: AbstractSVE +struct SamplingSVE{T<:AbstractFloat,K<:AbstractKernel} <: AbstractSVE kernel::K - ε::Float64 + ε::T n_gauss::Int nsvals_hint::Int - rule::Rule{Float64} - segs_x::Vector{Float64} - segs_y::Vector{Float64} - gauss_x::Rule{Float64} - gauss_y::Rule{Float64} - sqrtw_x::Vector{Float64} - sqrtw_y::Vector{Float64} + rule::Rule{T} + segs_x::Vector{T} + segs_y::Vector{T} + gauss_x::Rule{T} + gauss_y::Rule{T} + sqrtw_x::Vector{T} + sqrtw_y::Vector{T} end function SamplingSVE(kernel, ε; n_gauss=-1, T=Float64) @@ -155,9 +155,8 @@ function compute_sve( sve_strat::Type{Y}=iscentrosymmetric(kernel) ? CentrosymmSVE : SamplingSVE, svd_strat::Symbol=:default, )::Tuple{ - PiecewiseLegendrePolyArray{X,1},Vector{X},PiecewiseLegendrePolyArray{X,1} + PiecewiseLegendrePolyVector{X},Vector{X},PiecewiseLegendrePolyVector{X} } where {X,Y} - # if isnothing(ε) || isnothing(Twork) || isnothing(svd_strat) ε, Twork, default_svd_strat = _choose_accuracy(ε, Twork) end @@ -220,8 +219,8 @@ function postprocess(sve::SamplingSVE, u, s, v, T::Union{Type{X},Nothing}=nothin v_data .*= sqrt.(0.5 * dsegs_y') # Construct polynomials - ulx = PiecewiseLegendrePolyArray(T.(u_data), T.(sve.segs_x)) - vly = PiecewiseLegendrePolyArray(T.(v_data), T.(sve.segs_y)) + ulx = PiecewiseLegendrePolyVector(T.(u_data), T.(sve.segs_x)) + vly = PiecewiseLegendrePolyVector(T.(v_data), T.(sve.segs_y)) _canonicalize!(ulx, vly) return ulx, s, vly end @@ -261,8 +260,8 @@ function postprocess(sve::CentrosymmSVE, u, s, v, ::Type{T}) where {T} v_neg_data = reverse(v_pos_data; dims=2) .* poly_flip_x * signs[i] u_data = [u_neg_data;; u_pos_data] v_data = [v_neg_data;; v_pos_data] - u_complete[i] = PiecewiseLegendrePoly(u_data, segs_x; symm=signs[i]) - v_complete[i] = PiecewiseLegendrePoly(v_data, segs_y; symm=signs[i]) + u_complete[i] = PiecewiseLegendrePoly(u_data, segs_x, i - 1; symm=signs[i]) + v_complete[i] = PiecewiseLegendrePoly(v_data, segs_y, i - 1; symm=signs[i]) end return u_complete, s, v_complete diff --git a/test/_linalg.jl b/test/_linalg.jl index 129570d..ce9041b 100644 --- a/test/_linalg.jl +++ b/test/_linalg.jl @@ -2,51 +2,48 @@ using Test using LinearAlgebra using SparseIR using SparseIR._LinAlg - -@testset "linalg" begin - -@testset "jacobi" begin - A = randn(20, 10) - A_svd = svd_jacobi(A) - A_rec = A_svd.U * (A_svd.S .* A_svd.Vt) - @test isapprox(A_rec, A) -end - -@testset "rrqr" begin - A = randn(40, 30) - A_eps = norm(A) * eps(eltype(A)) - A_qr, A_rank = rrqr(A) - A_rec = A_qr.Q * A_qr.R * A_qr.P' - @test isapprox(A_rec, A; rtol=0, atol=4 * A_eps) - @test A_rank == 30 -end - -@testset "rrqr_trunc" begin - # Vandermonde matrix - A = Vector(-1:0.02:1) .^ Vector(0:20)' - m, n = size(A) - A_qr, k = SparseIR._LinAlg.rrqr(A, rtol=1e-5) - @test k < min(m, n) - - Q, R = SparseIR._LinAlg.truncate_qr_result(A_qr, k) - A_rec = Q * R * A_qr.P' - @test isapprox(A, A_rec, rtol=0, atol=1e-5 * norm(A)) -end - -@testset "tsvd" begin - A = Vector(-1:0.01:1) .^ Vector(0:50)' - A_tsvd = SparseIR._LinAlg.tsvd(A, rtol=1e-14) - k = length(A_tsvd.S) - atol = 1e-14 * norm(A) - @test isapprox(A_tsvd.U * Diagonal(A_tsvd.S) * A_tsvd.Vt, A, - rtol=0, atol=atol) - @test isapprox(A_tsvd.U' * A_tsvd.U, I, rtol=0, atol=1e-14) - @test isapprox(A_tsvd.V' * A_tsvd.V, I, rtol=0, atol=1e-14) - @test issorted(@view A_tsvd.S[end:-1:begin]) - @test k < minimum(size(A)) - - A_svd = svd(A) - @test isapprox(A_svd.S[1:k], A_tsvd.S, rtol=0, atol=atol) -end - +using DoubleFloats + +@testset "_linalg.jl" begin + @testset "jacobi with T = $T" for T in (Float64, Double64) + A = randn(T, 20, 10) + U, S, V = svd_jacobi(A) + @test U * Diagonal(S) * V' ≈ A + end + + @testset "rrqr with T = $T" for T in (Float64, Double64) + A = randn(T, 40, 30) + A_eps = norm(A) * eps(eltype(A)) + A_qr, A_rank = rrqr(A) + A_rec = A_qr.Q * A_qr.R * A_qr.P' + @test isapprox(A_rec, A; rtol=0, atol=4 * A_eps) + @test A_rank == 30 + end + + @testset "rrqr_trunc with T = $T" for T in (Float64, Double64) + # Vandermonde matrix + A = Vector{T}(-1:0.02:1) .^ Vector{T}(0:20)' + m, n = size(A) + A_qr, k = SparseIR._LinAlg.rrqr(A; rtol=1e-5) + @test k < min(m, n) + + Q, R = SparseIR._LinAlg.truncate_qr_result(A_qr, k) + A_rec = Q * R * A_qr.P' + @test isapprox(A, A_rec, rtol=0, atol=1e-5 * norm(A)) + end + + @testset "tsvd with T = $T" for T in (Float64, Double64), tol in (1e-14, 1e-13) + A = Vector{T}(-1:0.01:1) .^ Vector{T}(0:50)' + U, S, V = SparseIR._LinAlg.tsvd(A; rtol=tol) + k = length(S) + + @test U * Diagonal(S) * V' ≈ A rtol = 0 atol = tol * norm(A) + @test U'U ≈ I + @test V'V ≈ I + @test issorted(S; rev=true) + @test k < minimum(size(A)) + + A_svd = svd(Float64.(A)) + @test S ≈ A_svd.S[1:k] + end end diff --git a/test/augment.jl b/test/augment.jl index 4cc52bc..8502c75 100644 --- a/test/augment.jl +++ b/test/augment.jl @@ -43,8 +43,8 @@ using AssociatedLegendrePolynomials: Plm giv = 1 ./ ((im * π / β) * matsu_smpl.sampling_points .- pole) gl_from_matsu = fit(matsu_smpl, giv) - #println(maximum(abs.(gl_from_τ-gl_from_matsu))) - #println(maximum(abs.(gl_from_τ))) + #println(maximum(abs, gl_from_τ-gl_from_matsu)) + #println(maximum(abs, gl_from_τ)) #println("gl_from_τ", gl_from_τ[1:4]) #println("gl_from_matsu", gl_from_matsu[1:4]) @test isapprox( diff --git a/test/conftest.jl b/test/conftest.jl index 3ae475f..425b8af 100644 --- a/test/conftest.jl +++ b/test/conftest.jl @@ -1,5 +1,7 @@ -const sve_logistic = Dict( - 10 => SparseIR.compute_sve(LogisticKernel(10.0)), - 42 => SparseIR.compute_sve(LogisticKernel(42.0)), - 10_000 => SparseIR.compute_sve(LogisticKernel(10_000.0)), -) +if !@isdefined sve_logistic + const sve_logistic = Dict( + 10 => SparseIR.compute_sve(LogisticKernel(10.0)), + 42 => SparseIR.compute_sve(LogisticKernel(42.0)), + 10_000 => SparseIR.compute_sve(LogisticKernel(10_000.0)), + ) +end \ No newline at end of file diff --git a/test/poly.jl b/test/poly.jl index 2b63a29..96ccf02 100644 --- a/test/poly.jl +++ b/test/poly.jl @@ -49,7 +49,7 @@ using SparseIR @testset "matrix_hat" begin u, s, v = sve_logistic[42] - uhat = hat.(u, :odd, 0:(length(u) - 1)) # TODO: fix this + uhat = hat.(u, :odd) n = [1, 3, 5, -1, -3, 5] result1 = uhat[1](n) @@ -78,7 +78,7 @@ using SparseIR @testset "eval unique" begin u, s, v = sve_logistic[42] - û = hat.(u, :odd, 0:(length(u) - 1)) # TODO: fix this + û = hat.(u, :odd) # evaluate res1 = û([1, 3, 3, 1]) diff --git a/test/runtests.jl b/test/runtests.jl index bd9a9a6..d43e988 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,7 @@ using SparseIR include("conftest.jl") include("_util.jl") -@testset "SparseIR.jl" begin +@testset verbose=true "SparseIR.jl" begin include("gauss.jl") include("kernel.jl") include("poly.jl") @@ -16,6 +16,9 @@ include("_util.jl") include("bessels.jl") include("composite.jl") include("spr.jl") + include("_linalg.jl") # Works only if Mathematica and MathLink.jl are available. # include("_bessels.jl") end + +nothing diff --git a/test/sampling.jl b/test/sampling.jl index 0262509..3c7c3c9 100644 --- a/test/sampling.jl +++ b/test/sampling.jl @@ -63,11 +63,11 @@ using Random, LinearAlgebra Giw = evaluate(smpl, Gl) noise = 1e-5 - Gwn_n = Giw + noise * norm(Giw) * randn(size(Giw)...) - @inferred fit(smpl, Gwn_n) - @inferred fit(smpl, Gwn_n, dim=1) - Gl_n = fit(smpl, Gwn_n) + Giwn_n = Giw + noise * norm(Giw) * randn(size(Giw)...) + @inferred fit(smpl, Giwn_n) + @inferred fit(smpl, Giwn_n, dim=1) + Gl_n = fit(smpl, Giwn_n) - @test Gl ≈ Gl_n atol = 12 * noise * Gl_magn rtol = 0 + @test isapprox(Gl, Gl_n, atol=12 * noise * Gl_magn, rtol=0) broken = true end end diff --git a/test/spr.jl b/test/spr.jl index 614a7ac..3955446 100644 --- a/test/spr.jl +++ b/test/spr.jl @@ -3,36 +3,39 @@ using SparseIR using Random -@testset "spr.transform with stat = $stat" for stat in (fermion,) - beta = 1e4 - wmax = 1.0 - eps = 2e-8 # TODO: should be 1e-12, fix once extended precision works - basis = FiniteTempBasis(stat, beta, wmax, eps) - spr = SparsePoleRepresentation(basis) - - Random.seed!(4711) - - num_poles = 10 - poles = wmax * (2 * rand(num_poles) .- 1) - coeffs = 2 * rand(num_poles) .- 1 - @test maximum(abs, poles) <= wmax - - Gl = to_IR(SparsePoleRepresentation(basis, poles), coeffs) - - g_spr = from_IR(spr, Gl) - - # Comparison on Matsubara frequencies - smpl = MatsubaraSampling(basis) - smpl_for_spr = MatsubaraSampling(spr, smpl.sampling_points) - giv = evaluate(smpl_for_spr, g_spr) - giv_ref = evaluate(smpl, Gl; dim=1) - @test isapprox(giv, giv_ref; atol=300 * eps, rtol=0) - - # Comparison on tau - smpl_tau = TauSampling(basis) - gtau = evaluate(smpl_tau, Gl) - smpl_tau_for_spr = TauSampling(spr) - gtau2 = evaluate(smpl_tau_for_spr, g_spr) - - @test isapprox(gtau, gtau2; atol=300 * eps, rtol=0) -end +@testset "spr.jl" begin + @testset "transform with stat = $stat" for stat in (fermion, ) + # TODO: fix stat = boson + beta = 1e4 + wmax = 1.0 + eps = Double64(1e-12) # TODO: should be 1e-12, fix once extended precision works + basis = FiniteTempBasis(stat, beta, wmax, eps) + spr = SparsePoleRepresentation(basis) + + Random.seed!(4711) + + num_poles = 10 + poles = wmax * (2 * rand(num_poles) .- 1) + coeffs = 2 * rand(num_poles) .- 1 + @test maximum(abs, poles) ≤ wmax + + Gl = to_IR(SparsePoleRepresentation(basis, poles), coeffs) + + g_spr = from_IR(spr, Gl) + + # Comparison on Matsubara frequencies + smpl = MatsubaraSampling(basis) + smpl_for_spr = MatsubaraSampling(spr, smpl.sampling_points) + giv = evaluate(smpl_for_spr, g_spr) + giv_ref = evaluate(smpl, Gl; dim=1) + @test isapprox(giv, giv_ref; atol=300 * eps, rtol=0) + + # Comparison on tau + smpl_tau = TauSampling(basis) + gtau = evaluate(smpl_tau, Gl) + smpl_tau_for_spr = TauSampling(spr) + gtau2 = evaluate(smpl_tau_for_spr, g_spr) + + @test isapprox(gtau, gtau2; atol=300 * eps, rtol=0) + end +end \ No newline at end of file