From e520de1c097789d3250922cda8934a391b17bcda Mon Sep 17 00:00:00 2001 From: Samuel Badr Date: Mon, 23 Jun 2025 16:27:53 +0200 Subject: [PATCH 1/2] Compatibility with prospective v0.5 of QuanticsGrids --- Project.toml | 8 +-- src/tciinterface.jl | 45 +++++++--------- test/test_fouriertransform.jl | 52 +++++++++++++++++-- test/test_tciinterface.jl | 98 ++++++++++++++++++++++++++++++++++- test/test_with_aqua.jl | 2 +- 5 files changed, 170 insertions(+), 35 deletions(-) diff --git a/Project.toml b/Project.toml index c792615..feaf2b1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuanticsTCI" uuid = "b11687fd-3a1c-4c41-97d0-998ab401d50e" authors = ["Ritter.Marc , Hiroshi Shinaoka and contributors"] -version = "0.7.1" +version = "0.7.2" [deps] BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" @@ -11,7 +11,8 @@ TensorCrossInterpolation = "b261b2ec-6378-4871-b32e-9173bb050604" [compat] BitIntegers = "0.3.5" -QuanticsGrids = "0.3" +QuanticsGrids = "0.3,0.5" +StaticArrays = "1.9.13" TensorCrossInterpolation = "0.9.16" julia = "1.6" @@ -20,7 +21,8 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "Random", "Aqua", "JET", "Glob"] +test = ["Test", "Random", "Aqua", "JET", "Glob", "StaticArrays"] diff --git a/src/tciinterface.jl b/src/tciinterface.jl index edb7dec..3944ce7 100644 --- a/src/tciinterface.jl +++ b/src/tciinterface.jl @@ -6,8 +6,8 @@ end function evaluate( qtci::QuanticsTensorCI2{ValueType}, - indices::Union{Array{Int},NTuple{N,Int}} -)::ValueType where {N,ValueType} + indices::Union{Array{Int},Tuple{Vararg{Int}}} +)::ValueType where {ValueType} bitlist = QG.grididx_to_quantics(qtci.grid, Tuple(indices)) return TensorCrossInterpolation.evaluate(qtci.tci, bitlist) end @@ -35,9 +35,9 @@ end function cachedata(qtci::QuanticsTensorCI2{V}) where {V} return Dict( - QG.quantics_to_origcoord(qtci.grid, k) => v - for (k, v) in TCI.cachedata(qtci.quanticsfunction) - ) + QG.quantics_to_origcoord(qtci.grid, k) => v + for (k, v) in TCI.cachedata(qtci.quanticsfunction) + ) end @doc raw""" @@ -75,13 +75,15 @@ function quanticscrossinterpolate( nrandominitpivot=5, kwargs... ) where {ValueType,n} - R = grid.R + # R = grid.R + + qlocaldimensions = QG.localdimensions(grid) + # if grid.unfoldingscheme === :interleaved + # fill(2, n * R) + # else + # fill(2^n, R) + # end - qlocaldimensions = if grid.unfoldingscheme === :interleaved - fill(2, n * R) - else - fill(2^n, R) - end qf_ = (n == 1 ? q -> f(only(QG.quantics_to_origcoord(grid, q))) @@ -102,7 +104,7 @@ function quanticscrossinterpolate( if keytype === BigInt @warn "Using BigInt as key type. This will lead to significant memory usage and performance degradation." end - qf = TCI.CachedFunction{ValueType, keytype}(qf_, qlocaldimensions) + qf = TCI.CachedFunction{ValueType,keytype}(qf_, qlocaldimensions) qinitialpivots = (initialpivots === nothing ? [ones(Int, length(qlocaldimensions))] @@ -158,18 +160,14 @@ function quanticscrossinterpolate( kwargs... ) where {ValueType} localdimensions = log2.(length.(xvals)) - if !allequal(localdimensions) - throw(ArgumentError( - "This method only supports grids with equal number of points in each direction. If you need a different grid, please use index_to_quantics and quantics_to_index and determine the index ordering yourself.")) - elseif !all(isinteger.(localdimensions)) + if !all(isinteger.(localdimensions)) throw(ArgumentError("This method only supports grid sizes that are powers of 2.")) end - n = length(localdimensions) - R = Int(first(localdimensions)) - grid = QG.DiscretizedGrid{n}(R, Tuple(minimum.(xvals)), Tuple(maximum.(xvals)); unfoldingscheme=unfoldingscheme, includeendpoint=true) + R = Tuple(Int.(localdimensions)) + grid = QG.DiscretizedGrid(R, Tuple(minimum.(xvals)), Tuple(maximum.(xvals)); unfoldingscheme, includeendpoint=true) - return quanticscrossinterpolate(ValueType, f, grid, initialpivots; nrandominitpivot=nrandominitpivot, kwargs...) + return quanticscrossinterpolate(ValueType, f, grid, initialpivots; nrandominitpivot, kwargs...) end @doc raw""" @@ -221,14 +219,11 @@ function quanticscrossinterpolate( kwargs... ) where {ValueType,d} localdimensions = log2.(size) - if !allequal(localdimensions) - throw(ArgumentError( - "This method only supports grids with equal number of points in each direction. If you need a different grid, please use index_to_quantics and quantics_to_index and determine the index ordering yourself.")) - elseif !all(isinteger.(localdimensions)) + if !all(isinteger.(localdimensions)) throw(ArgumentError("This method only supports grid sizes that are powers of 2.")) end - R = Int(first(localdimensions)) + R = Tuple(Int.(localdimensions)) grid = QG.InherentDiscreteGrid{d}(R; unfoldingscheme=unfoldingscheme) return quanticscrossinterpolate(ValueType, f, grid, initialpivots; kwargs...) end diff --git a/test/test_fouriertransform.jl b/test/test_fouriertransform.jl index 6f3cdc2..2e375d3 100644 --- a/test/test_fouriertransform.jl +++ b/test/test_fouriertransform.jl @@ -3,25 +3,69 @@ import TensorCrossInterpolation as TCI import QuanticsGrids as QG import Random +# Legacy functions from QuanticsGrids <= v0.4 +module LegacyQuanticsGrids + +using StaticArrays + +function quantics_to_index_fused( + digitlist::AbstractVector{<:Integer}; + base::Integer=2, + dims::Val{d}=Val(1), +)::NTuple{d,Int} where {d} + R = length(digitlist) + result = ones(MVector{d,Int}) + + maximum(digitlist) <= base^d || error("maximum(digitlist) <= base^d") + minimum(digitlist) >= 0 || error("minimum(digitlist) >= 0") + + for n = 1:R # from the least to most significant digit + scale = base^(n - 1) # length scale + tmp = digitlist[R-n+1] - 1 + for i = 1:d # in the order of 1st dim, 2nd dim, ... + div_, rem_ = divrem(tmp, base) + result[i] += rem_ * scale + tmp = div_ + end + end + + return tuple(result...) +end + +function index_to_quantics!(digitlist, index::Integer; base::Integer=2) + numdigits = length(digitlist) + for i = 1:numdigits + digitlist[i] = mod(index - 1, base^(numdigits - i + 1)) ÷ base^(numdigits - i) + 1 + end + return digitlist +end + +function index_to_quantics(index::Integer; numdigits=8, base::Integer=2) + digitlist = Vector{Int}(undef, numdigits) + return index_to_quantics!(digitlist, index; base=base) +end + +end # module LegacyQuanticsGrids + @testset "Quantics Fourier Transform, R=$R" for R in [4, 16, 62] Random.seed!(23593243) r = 12 coeffs = randn(ComplexF64, r) fm(x) = sum(coeffs .* cispi.(2 * (0:r-1) * x)) - fq(q) = fm((QG.quantics_to_index_fused(q)[1] - 1) / 2^big(R)) + fq(q) = fm((LegacyQuanticsGrids.quantics_to_index_fused(q)[1] - 1) / 2^big(R)) qtci, = TCI.crossinterpolate2(ComplexF64, fq, fill(2, R); tolerance=1e-14) fouriertt = QTCI.quanticsfouriermpo(R; normalize=false) / 2^big(R) qtcif = TCI.contract(fouriertt, qtci) for i in 1:min(r, 2^big(R)) - q = QG.index_to_quantics(i, numdigits=R) + q = LegacyQuanticsGrids.index_to_quantics(i, numdigits=R) @test qtcif(reverse(q)) ≈ coeffs[i] end - for i in Int.(round.(range(r+2, 2^big(R); length=100))) - q = QG.index_to_quantics(i, numdigits=R) + for i in Int.(round.(range(r + 2, 2^big(R); length=100))) + q = LegacyQuanticsGrids.index_to_quantics(i, numdigits=R) @test abs(qtcif(reverse(q))) < 1e-12 end end diff --git a/test/test_tciinterface.jl b/test/test_tciinterface.jl index 7391483..3f4126d 100644 --- a/test/test_tciinterface.jl +++ b/test/test_tciinterface.jl @@ -103,9 +103,103 @@ end @testset "quanticscrossinterpolate for integrals" begin R = 40 xgrid = QG.DiscretizedGrid{1}(R, 0, 1) - F(x) = sin(1/(x^2 + 0.01)) - f(x) = -2*x * cos(1/(x^2 + 0.01)) / (x^2 + 0.01)^2 + F(x) = sin(1 / (x^2 + 0.01)) + f(x) = -2 * x * cos(1 / (x^2 + 0.01)) / (x^2 + 0.01)^2 tci, ranks, errors = quanticscrossinterpolate(Float64, f, xgrid; tolerance=1e-13) @test sum(tci) * QG.grid_step(xgrid) ≈ F(1) - F(0) @test integral(tci) ≈ F(1) - F(0) end + +@testset "quanticscrossinterpolate with multi-resolution grids" for unfoldingscheme in [ + :interleaved, + :fused +] + # Test with different resolutions in each dimension + f(x, y) = 0.1 * x^2 + 0.01 * y^3 - pi * x * y + 5 + + # Different grid sizes: 2^6 = 64 points in x, 2^4 = 16 points in y + # This should work with the new multi-resolution support + xvals = range(-3, 2; length=64) # 2^6 points + yvals = range(-17, 12; length=16) # 2^4 points + + qtt, ranks, errors = quanticscrossinterpolate( + Float64, f, [xvals, yvals]; unfoldingscheme, tolerance=1e-8) + @test last(errors) < 1e-8 + + # Test evaluation at grid points + for (i, x) in enumerate(xvals) + for (j, y) in enumerate(yvals) + @test f(x, y) ≈ qtt(i, j) + end + end +end + +@testset "quanticscrossinterpolate with multi-resolution DiscretizedGrid" for unfoldingscheme in [ + :interleaved, + :fused +] + # Test with different R values per dimension + R = (5, 3) # 2^5 = 32 points in x, 2^3 = 8 points in y + f(x, y) = 0.1 * x^2 + 0.01 * y^3 - pi * x * y + 5 + + grid = QG.DiscretizedGrid( + R, + (-3, -17), + (2, 12); + unfoldingscheme + ) + + qtt, ranks, errors = quanticscrossinterpolate(Float64, f, grid; tolerance=1e-8) + @test last(errors) < 1e-8 + + # Test evaluation at all grid points + for i in 1:2^R[1] + for j in 1:2^R[2] + @test f(QG.grididx_to_origcoord(grid, (i, j))...) ≈ qtt(i, j) + end + end +end + +@testset "quanticscrossinterpolate with multi-resolution InherentDiscreteGrid" for unfoldingscheme in [ + :interleaved, + :fused +] + # Test with different R values per dimension + R = (3, 2, 4) # 8x4x16 tensor + A = rand(2^R[1], 2^R[2], 2^R[3]) + + grid = QG.InherentDiscreteGrid{3}(R; unfoldingscheme) + qtt, ranks, errors = quanticscrossinterpolate( + Float64, (i...) -> A[i...], grid; tolerance=1e-8) + @test last(errors) < 1e-8 + + for i in CartesianIndices(size(A)) + @test A[i] ≈ qtt(Tuple(i)) + end +end + +@testset "quanticscrossinterpolate with multi-resolution size overload" for unfoldingscheme in [ + :interleaved, + :fused +] + # Test the size-based overload with different dimensions + # This should work once the allequal check is removed + A = rand(8, 16, 4) # 2^3 x 2^4 x 2^2 - different resolutions per dimension + + qtt, ranks, errors = quanticscrossinterpolate( + Float64, (i...) -> A[i...], size(A); unfoldingscheme, tolerance=1e-8) + @test last(errors) < 1e-8 + + for i in CartesianIndices(size(A)) + @test A[i] ≈ qtt(Tuple(i)) + end + + # Test array overload as well + qtt2, ranks2, errors2 = quanticscrossinterpolate( + A; unfoldingscheme, tolerance=1e-8) + @test last(errors2) < 1e-8 + + for i in CartesianIndices(size(A)) + @test A[i] ≈ qtt2(Tuple(i)) + end +end diff --git a/test/test_with_aqua.jl b/test/test_with_aqua.jl index 46eecca..6688fb3 100644 --- a/test/test_with_aqua.jl +++ b/test/test_with_aqua.jl @@ -2,5 +2,5 @@ using Aqua import QuanticsTCI @testset "Aqua" begin - Aqua.test_all(QuanticsTCI; ambiguities = false, unbound_args = false, deps_compat = false) + Aqua.test_all(QuanticsTCI; deps_compat=false) end From b57b4dc8d52aa48851e5e8eeccf2449220a3eef0 Mon Sep 17 00:00:00 2001 From: Samuel Badr Date: Mon, 23 Jun 2025 16:43:02 +0200 Subject: [PATCH 2/2] restrict QuanticsGrids dependency to v0.5 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index feaf2b1..0e1de7f 100644 --- a/Project.toml +++ b/Project.toml @@ -11,7 +11,7 @@ TensorCrossInterpolation = "b261b2ec-6378-4871-b32e-9173bb050604" [compat] BitIntegers = "0.3.5" -QuanticsGrids = "0.3,0.5" +QuanticsGrids = "0.5" StaticArrays = "1.9.13" TensorCrossInterpolation = "0.9.16" julia = "1.6"