From 6b5bd7fc49f8e1f5c0b87d6025849586abaff938 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Mon, 6 Apr 2026 18:09:07 +0900 Subject: [PATCH 1/2] add quantics transform parity wrappers --- AGENTS.md | 5 + README.md | 211 +++++++++++++++++---------------- src/C_API.jl | 140 ++++++++++++++++++++++ src/QuanticsGrids.jl | 9 +- src/QuanticsTransform.jl | 140 +++++++++++++++++++++- src/Tensor4all.jl | 2 + src/TreeTN.jl | 45 +++++++ test/runtests.jl | 2 + test/test_quanticsgrids.jl | 22 ++++ test/test_quanticstransform.jl | 110 +++++++++++++++++ 10 files changed, 578 insertions(+), 108 deletions(-) create mode 100644 test/test_quanticsgrids.jl create mode 100644 test/test_quanticstransform.jl diff --git a/AGENTS.md b/AGENTS.md index d529b7d..86a9b66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,11 @@ As of commit `ca97593` (PR #302), tensor4all-rs uses **column-major** storage fo ## Build & Test - Rust library: `deps/build.jl` builds `libtensor4all_capi.so` from `tensor4all-rs` +- When running `deps/build.jl` directly, use `julia --startup-file=no --project=. deps/build.jl` - Rust source resolution: `TENSOR4ALL_RS_PATH` env var > sibling `../tensor4all-rs/` > GitHub clone - Tests: `Pkg.test()` or `julia test/runtests.jl` (requires ITensors.jl in test deps) - Skip HDF5 tests: `T4A_SKIP_HDF5_TESTS=1` + +## PR Checklist + +- Before opening or updating a PR, review `README.md` and update user-facing examples and workflow notes if the API or expected commands changed. diff --git a/README.md b/README.md index acec74d..3952dca 100644 --- a/README.md +++ b/README.md @@ -18,55 +18,48 @@ After installation, open a new terminal or run `source ~/.cargo/env`. ## Installation -The Rust shared library is **automatically compiled** by `Pkg.build()`. No manual build steps are required. +The Rust shared library is compiled automatically by `Pkg.build()`. -### Option 1: Develop locally (for package development) +### Option 1: Develop locally ```julia using Pkg Pkg.activate("/path/to/Tensor4all.jl") -Pkg.build() # Automatically compiles the Rust backend +Pkg.build() ``` -The shared library is installed to: -``` -/path/to/Tensor4all.jl/deps/libtensor4all_capi.{dylib,so,dll} -``` - -### Option 2: Develop from another environment or global +### Option 2: Develop from another environment ```julia using Pkg Pkg.develop(path="/path/to/Tensor4all.jl") -Pkg.build("Tensor4all") # Automatically compiles the Rust backend -``` - -The shared library is installed to: -``` -/path/to/Tensor4all.jl/deps/libtensor4all_capi.{dylib,so,dll} +Pkg.build("Tensor4all") ``` -(Same location as the source — `Pkg.develop` symlinks to the local directory.) -### Option 3: Add from another environment (e.g., from GitHub URL) +### Option 3: Add from GitHub ```julia using Pkg Pkg.add(url="https://github.com/tensor4all/Tensor4all.jl.git") -Pkg.build("Tensor4all") # Automatically compiles the Rust backend +Pkg.build("Tensor4all") ``` -The shared library is installed to: -``` -~/.julia/packages/Tensor4all//deps/libtensor4all_capi.{dylib,so,dll} -``` +The built shared library lives in `deps/libtensor4all_capi.{dylib,so,dll}` inside +the package directory. ### Rust source resolution -The build script locates the `tensor4all-rs` Rust workspace in this priority order: +`deps/build.jl` looks for `tensor4all-rs` in this order: + +1. `TENSOR4ALL_RS_PATH` +2. sibling directory `../tensor4all-rs/` +3. clone from GitHub -1. `TENSOR4ALL_RS_PATH` environment variable -2. Sibling directory `../tensor4all-rs/` (relative to the package root) -3. Automatic clone from GitHub +If you run the build script directly, use the package project: + +```bash +julia --startup-file=no --project=. deps/build.jl +``` ## Running Tests @@ -76,7 +69,17 @@ Pkg.activate("/path/to/Tensor4all.jl") Pkg.test() ``` -To skip HDF5 tests: set `T4A_SKIP_HDF5_TESTS=1` before running. +To skip HDF5 tests, set `T4A_SKIP_HDF5_TESTS=1`. + +## Modules + +- `Tensor4all`: `Index`, `Tensor`, `onehot`, HDF5 save/load +- `Tensor4all.SimpleTT`: simple tensor trains with fixed site dimensions +- `Tensor4all.TreeTN`: MPS/MPO/tree tensor network operations +- `Tensor4all.QuanticsGrids`: coordinate transforms between physical and quantics grids +- `Tensor4all.QuanticsTransform`: quantics shift/flip/phase/cumsum/Fourier/affine operators +- `Tensor4all.QuanticsTCI`: quantics tensor cross interpolation +- `Tensor4all.TreeTCI`: tree-structured tensor cross interpolation ## Usage @@ -84,122 +87,120 @@ To skip HDF5 tests: set `T4A_SKIP_HDF5_TESTS=1` before running. using Tensor4all ``` -### Index +### Index and Tensor ```julia -# Create an index with dimension 5 -i = Index(5) - -# Create an index with tags +i = Index(2) j = Index(3; tags="Site,n=1") -# Access properties -dim(i) # 5 -id(i) # unique UInt64 ID +dim(i) # 2 tags(j) # "Site,n=1" hastag(j, "Site") # true -# Copy an index (same ID) -j2 = copy(j) +t = Tensor([i, j], rand(2, 3)) +rank(t) # 2 +dims(t) # (2, 3) +storage_kind(t) # DenseF64 +indices(t) # [i, j] -# Create a similar index (new ID, same dim and tags) -j3 = sim(j) +z = Tensor([i, j], rand(ComplexF64, 2, 3)) +oh = onehot(i => 1, j => 2) ``` -### Tensor +### Simple Tensor Trains ```julia -i = Index(2) -j = Index(3) - -# Create a dense Float64 tensor -data = rand(2, 3) -t = Tensor([i, j], data) - -# Access properties -rank(t) # 2 -dims(t) # (2, 3) -storage_kind(t) # DenseF64 -indices(t) # [i, j] - -# Retrieve data (column-major Julia array) -retrieved = Tensor4all.data(t) - -# Retrieve data in a specific index order -arr = Array(t, [j, i]) # shape (3, 2), transposed - -# Create a complex tensor -z = Tensor([i, j], rand(ComplexF64, 2, 3)) +using Tensor4all.SimpleTT -# Create a higher-rank tensor -k = Index(4) -t3 = Tensor([i, j, k], rand(2, 3, 4)) +tt = SimpleTensorTrain([2, 3, 4], 1.0) -# Create a one-hot tensor -oh = onehot(i => 1, j => 2) # 1.0 at position [1, 2] +tt(0, 0, 0) # 1.0 +Tensor4all.SimpleTT.site_dims(tt) # [2, 3, 4] +Tensor4all.SimpleTT.link_dims(tt) # [] +Tensor4all.SimpleTT.site_tensor(tt, 0) +sum(tt) ``` -### MPS (Matrix Product State) / Tensor Train +### Tree Tensor Networks ```julia using Tensor4all.TreeTN -# Create a random MPS sites = [Index(2) for _ in 1:5] mps = random_mps(sites; linkdims=4) -# Properties -length(mps) # 5 -nv(mps) # 5 (number of vertices) -ne(mps) # 4 (number of edges) -maxbonddim(mps) # 4 -linkdims(mps) # [4, 4, 4, 4] +length(mps) # 5 +nv(mps) # 5 +ne(mps) # 4 +maxbonddim(mps) # 4 -# Access tensors (1-indexed) -mps[1] # first tensor -collect(mps) # all tensors as a vector - -# Orthogonalize (QR-based canonical form) orthogonalize!(mps, 3) -canonical_form(mps) # Unitary - -# Other canonical forms: LU, CI -orthogonalize!(mps, 1; form=LU) - -# Truncate bond dimensions truncate!(mps; maxdim=2) +inner(mps, mps) +to_dense(mps) +``` -# Inner product and norm -ip = inner(mps, mps) -n = norm(mps) +`SimpleTensorTrain` can also be converted to an MPS: -# Contract to a dense tensor -dense = to_dense(mps) +```julia +using Tensor4all.SimpleTT: SimpleTensorTrain +using Tensor4all.TreeTN: MPS -# Contract two MPS/MPO -result = contract(mps_a, mps_b) # zipup (default) -result = contract(mps_a, mps_b; method=:fit) # fit -result = contract(mps_a, mps_b; method=:naive) # naive +mps = MPS(SimpleTensorTrain([2, 2, 2], 1.0)) ``` -### Tensor Cross Interpolation (TCI) +### Quantics Grids ```julia -using Tensor4all.TensorCI -using Tensor4all.SimpleTT +using Tensor4all: DiscretizedGrid, localdimensions +using Tensor4all.QuanticsGrids: origcoord_to_quantics, quantics_to_origcoord -# Approximate a function as a tensor train -f(i, j, k) = Float64((1 + i) * (1 + j) * (1 + k)) # 0-based indices -tt, err = crossinterpolate2(f, [3, 4, 5]; tolerance=1e-10) +grid = DiscretizedGrid(2, [2, 2], [0.0, 0.0], [1.0, 1.0]; unfolding=:grouped) -# Evaluate the tensor train -tt(0, 0, 0) # ≈ 1.0 -tt(2, 3, 4) # ≈ 60.0 +q = origcoord_to_quantics(grid, [0.25, 0.75]) +x = quantics_to_origcoord(grid, q) -# Sum over all elements -sum(tt) +length(q) # 4 +x # approximately [0.25, 0.75] +localdimensions(grid) # [2, 2, 2, 2] +``` + +### Quantics Transform + +```julia +using Tensor4all.SimpleTT: SimpleTensorTrain +using Tensor4all.TreeTN: MPS +using Tensor4all.QuanticsTransform + +mps = MPS(SimpleTensorTrain([2, 2, 2], 1.0)) + +op = shift_operator(3, 1) +set_iospaces!(op, mps) +shifted = apply(op, mps; method=:naive) + +multi = shift_operator_multivar(3, 1, 2, 0) +flipped = flip_operator_multivar(3, 2, 1; bc=Open) +phase = phase_rotation_operator_multivar(3, pi / 4, 2, 1) +aff = affine_operator( + 3, + Int64[1 -1; 1 0; 0 1], + ones(Int64, 3, 2), + Int64[0, 0, 0], + ones(Int64, 3); + bc=[Open, Periodic, Periodic], +) ``` +### Interpolation Modules + +High-level interpolation APIs are available in: + +- `Tensor4all.QuanticsTCI` +- `Tensor4all.TreeTCI` + +See the module docstrings in `src/QuanticsTCI.jl` and `src/TreeTCI.jl` for the +current entry points. + ### HDF5 Save/Load Tensors and MPS can be saved to HDF5 files in a format compatible with ITensors.jl. diff --git a/src/C_API.jl b/src/C_API.jl index 5028aaa..d51c4d1 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -1630,6 +1630,7 @@ end # Unfolding scheme enum (must match Rust side) const UNFOLDING_FUSED = Cint(0) const UNFOLDING_INTERLEAVED = Cint(1) +const UNFOLDING_GROUPED = Cint(2) """ t4a_qgrid_disc_new(ndims, rs_arr, lower_arr, upper_arr, unfolding, out) -> Cint @@ -2179,6 +2180,27 @@ function t4a_qtransform_shift(r::Csize_t, offset::Int64, bc::Cint, out) ) end +""" + t4a_qtransform_shift_multivar(r, offset, bc, nvariables, target_var, out) -> Cint + +Create a shift operator acting on one variable in a multi-variable quantics system. +""" +function t4a_qtransform_shift_multivar( + r::Csize_t, + offset::Int64, + bc::Cint, + nvariables::Csize_t, + target_var::Csize_t, + out, +) + return ccall( + _sym(:t4a_qtransform_shift_multivar), + Cint, + (Csize_t, Int64, Cint, Csize_t, Csize_t, Ptr{Ptr{Cvoid}}), + r, offset, bc, nvariables, target_var, out + ) +end + """ t4a_qtransform_flip(r, bc, out) -> Cint @@ -2193,6 +2215,26 @@ function t4a_qtransform_flip(r::Csize_t, bc::Cint, out) ) end +""" + t4a_qtransform_flip_multivar(r, bc, nvariables, target_var, out) -> Cint + +Create a flip operator acting on one variable in a multi-variable quantics system. +""" +function t4a_qtransform_flip_multivar( + r::Csize_t, + bc::Cint, + nvariables::Csize_t, + target_var::Csize_t, + out, +) + return ccall( + _sym(:t4a_qtransform_flip_multivar), + Cint, + (Csize_t, Cint, Csize_t, Csize_t, Ptr{Ptr{Cvoid}}), + r, bc, nvariables, target_var, out + ) +end + """ t4a_qtransform_phase_rotation(r, theta, out) -> Cint @@ -2207,6 +2249,26 @@ function t4a_qtransform_phase_rotation(r::Csize_t, theta::Cdouble, out) ) end +""" + t4a_qtransform_phase_rotation_multivar(r, theta, nvariables, target_var, out) -> Cint + +Create a phase rotation operator acting on one variable in a multi-variable quantics system. +""" +function t4a_qtransform_phase_rotation_multivar( + r::Csize_t, + theta::Cdouble, + nvariables::Csize_t, + target_var::Csize_t, + out, +) + return ccall( + _sym(:t4a_qtransform_phase_rotation_multivar), + Cint, + (Csize_t, Cdouble, Csize_t, Csize_t, Ptr{Ptr{Cvoid}}), + r, theta, nvariables, target_var, out + ) +end + """ t4a_qtransform_cumsum(r, out) -> Cint @@ -2237,6 +2299,56 @@ function t4a_qtransform_fourier(r::Csize_t, forward::Cint, maxbonddim::Csize_t, ) end +""" + t4a_qtransform_affine(r, a_num, a_den, b_num, b_den, m, n, bc, out) -> Cint + +Create a general affine transformation operator with rational coefficients. +`a_num`/`a_den` encode the MxN matrix in column-major order. +`b_num`/`b_den` encode the M-element translation vector. +`bc` has length `m`. +""" +function t4a_qtransform_affine( + r::Csize_t, + a_num, + a_den, + b_num, + b_den, + m::Csize_t, + n::Csize_t, + bc, + out, +) + return ccall( + _sym(:t4a_qtransform_affine), + Cint, + (Csize_t, Ptr{Int64}, Ptr{Int64}, Ptr{Int64}, Ptr{Int64}, Csize_t, Csize_t, Ptr{Cint}, Ptr{Ptr{Cvoid}}), + r, a_num, a_den, b_num, b_den, m, n, bc, out + ) +end + +""" + t4a_qtransform_binaryop(r, a1, b1, a2, b2, bc1, bc2, out) -> Cint + +Create a two-output binary-operation transform. +""" +function t4a_qtransform_binaryop( + r::Csize_t, + a1::Int8, + b1::Int8, + a2::Int8, + b2::Int8, + bc1::Cint, + bc2::Cint, + out, +) + return ccall( + _sym(:t4a_qtransform_binaryop), + Cint, + (Csize_t, Int8, Int8, Int8, Int8, Cint, Cint, Ptr{Ptr{Cvoid}}), + r, a1, b1, a2, b2, bc1, bc2, out + ) +end + """ t4a_linop_apply(op, state, method, rtol, maxdim, out) -> Cint @@ -2253,4 +2365,32 @@ function t4a_linop_apply(op::Ptr{Cvoid}, state::Ptr{Cvoid}, method::Cint, ) end +""" + t4a_linop_set_input_space(op, state) -> Cint + +Reset a linear operator's true input site indices to match a TreeTN state. +""" +function t4a_linop_set_input_space(op::Ptr{Cvoid}, state::Ptr{Cvoid}) + return ccall( + _sym(:t4a_linop_set_input_space), + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}), + op, state + ) +end + +""" + t4a_linop_set_output_space(op, state) -> Cint + +Reset a linear operator's true output site indices to match a TreeTN state. +""" +function t4a_linop_set_output_space(op::Ptr{Cvoid}, state::Ptr{Cvoid}) + return ccall( + _sym(:t4a_linop_set_output_space), + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}), + op, state + ) +end + end # module C_API diff --git a/src/QuanticsGrids.jl b/src/QuanticsGrids.jl index 0b526ba..2a9f7fc 100644 --- a/src/QuanticsGrids.jl +++ b/src/QuanticsGrids.jl @@ -29,6 +29,7 @@ export DiscretizedGrid, InherentDiscreteGrid export origcoord_to_quantics, quantics_to_origcoord export origcoord_to_grididx, grididx_to_origcoord export grididx_to_quantics, quantics_to_grididx +export localdimensions # ============================================================================ # Unfolding scheme helper @@ -39,8 +40,10 @@ function _unfolding_to_cint(unfolding::Symbol) return C_API.UNFOLDING_FUSED elseif unfolding == :interleaved return C_API.UNFOLDING_INTERLEAVED + elseif unfolding == :grouped + return C_API.UNFOLDING_GROUPED else - error("Unknown unfolding scheme: $unfolding. Use :fused or :interleaved.") + error("Unknown unfolding scheme: $unfolding. Use :fused, :interleaved, or :grouped.") end end @@ -149,6 +152,8 @@ function local_dimensions(g::DiscretizedGrid) return Int.(out[1:n_out[]]) end +localdimensions(g::DiscretizedGrid) = local_dimensions(g) + """ lower_bound(g::DiscretizedGrid) -> Vector{Float64} @@ -365,6 +370,8 @@ function local_dimensions(g::InherentDiscreteGrid) return Int.(out[1:n_out[]]) end +localdimensions(g::InherentDiscreteGrid) = local_dimensions(g) + """ origin(g::InherentDiscreteGrid) -> Vector{Int64} diff --git a/src/QuanticsTransform.jl b/src/QuanticsTransform.jl index 5b4c931..36c3ff1 100644 --- a/src/QuanticsTransform.jl +++ b/src/QuanticsTransform.jl @@ -22,7 +22,10 @@ using ..TreeTN: TreeTensorNetwork export LinearOperator export shift_operator, flip_operator, phase_rotation_operator, cumsum_operator, fourier_operator +export shift_operator_multivar, flip_operator_multivar, phase_rotation_operator_multivar +export affine_operator, binaryop_operator export apply +export set_input_space!, set_output_space!, set_iospaces! export BoundaryCondition, Periodic, Open # ============================================================================ @@ -42,6 +45,12 @@ Boundary condition for quantics operators. Open = 1 end +_bc_cint(bc::BoundaryCondition) = Cint(Int(bc)) + +function _bc_array_cint(bc::AbstractVector{<:BoundaryCondition}) + return Cint[Int(b) for b in bc] +end + # ============================================================================ # LinearOperator type # ============================================================================ @@ -83,7 +92,21 @@ Create a shift operator: f(x) = g(x + offset) mod 2^r. """ function shift_operator(r::Integer, offset::Integer; bc::BoundaryCondition=Periodic) out = Ref{Ptr{Cvoid}}(C_NULL) - status = C_API.t4a_qtransform_shift(Csize_t(r), Int64(offset), Cint(Int(bc)), out) + status = C_API.t4a_qtransform_shift(Csize_t(r), Int64(offset), _bc_cint(bc), out) + C_API.check_status(status) + return LinearOperator(out[]) +end + +""" + shift_operator_multivar(r::Integer, offset::Integer, nvariables::Integer, target_var::Integer; bc=Periodic) -> LinearOperator + +Create a shift operator acting on one variable in a multi-variable quantics system. +""" +function shift_operator_multivar(r::Integer, offset::Integer, nvariables::Integer, + target_var::Integer; bc::BoundaryCondition=Periodic) + out = Ref{Ptr{Cvoid}}(C_NULL) + status = C_API.t4a_qtransform_shift_multivar( + Csize_t(r), Int64(offset), _bc_cint(bc), Csize_t(nvariables), Csize_t(target_var), out) C_API.check_status(status) return LinearOperator(out[]) end @@ -99,7 +122,21 @@ Create a flip operator: f(x) = g(2^r - x). """ function flip_operator(r::Integer; bc::BoundaryCondition=Periodic) out = Ref{Ptr{Cvoid}}(C_NULL) - status = C_API.t4a_qtransform_flip(Csize_t(r), Cint(Int(bc)), out) + status = C_API.t4a_qtransform_flip(Csize_t(r), _bc_cint(bc), out) + C_API.check_status(status) + return LinearOperator(out[]) +end + +""" + flip_operator_multivar(r::Integer, nvariables::Integer, target_var::Integer; bc=Periodic) -> LinearOperator + +Create a flip operator acting on one variable in a multi-variable quantics system. +""" +function flip_operator_multivar(r::Integer, nvariables::Integer, target_var::Integer; + bc::BoundaryCondition=Periodic) + out = Ref{Ptr{Cvoid}}(C_NULL) + status = C_API.t4a_qtransform_flip_multivar( + Csize_t(r), _bc_cint(bc), Csize_t(nvariables), Csize_t(target_var), out) C_API.check_status(status) return LinearOperator(out[]) end @@ -120,6 +157,71 @@ function phase_rotation_operator(r::Integer, theta::Real) return LinearOperator(out[]) end +""" + phase_rotation_operator_multivar(r::Integer, theta::Real, nvariables::Integer, target_var::Integer) -> LinearOperator + +Create a phase rotation operator acting on one variable in a multi-variable quantics system. +""" +function phase_rotation_operator_multivar(r::Integer, theta::Real, nvariables::Integer, + target_var::Integer) + out = Ref{Ptr{Cvoid}}(C_NULL) + status = C_API.t4a_qtransform_phase_rotation_multivar( + Csize_t(r), Cdouble(theta), Csize_t(nvariables), Csize_t(target_var), out) + C_API.check_status(status) + return LinearOperator(out[]) +end + +""" + affine_operator(r::Integer, a_num::AbstractMatrix{<:Integer}, a_den::AbstractMatrix{<:Integer}, + b_num::AbstractVector{<:Integer}, b_den::AbstractVector{<:Integer}; bc) -> LinearOperator + +Create an affine transform with rational coefficients on quantized coordinates. +`bc` must contain one boundary condition per output variable. +""" +function affine_operator(r::Integer, + a_num::AbstractMatrix{<:Integer}, + a_den::AbstractMatrix{<:Integer}, + b_num::AbstractVector{<:Integer}, + b_den::AbstractVector{<:Integer}; + bc::AbstractVector{<:BoundaryCondition}) + size(a_num) == size(a_den) || error("a_num and a_den must have the same size") + m, n = size(a_num) + length(b_num) == m || error("b_num length must match the number of output variables") + length(b_den) == m || error("b_den length must match the number of output variables") + length(bc) == m || error("bc length must match the number of output variables") + + out = Ref{Ptr{Cvoid}}(C_NULL) + status = C_API.t4a_qtransform_affine( + Csize_t(r), + Int64.(vec(a_num)), + Int64.(vec(a_den)), + Int64.(collect(b_num)), + Int64.(collect(b_den)), + Csize_t(m), + Csize_t(n), + _bc_array_cint(bc), + out, + ) + C_API.check_status(status) + return LinearOperator(out[]) +end + +""" + binaryop_operator(r::Integer, a1::Integer, b1::Integer, a2::Integer, b2::Integer; + bc1=Periodic, bc2=Periodic) -> LinearOperator + +Create a two-output binary operator corresponding to +`(a1*x + b1*y, a2*x + b2*y)`. +""" +function binaryop_operator(r::Integer, a1::Integer, b1::Integer, a2::Integer, b2::Integer; + bc1::BoundaryCondition=Periodic, bc2::BoundaryCondition=Periodic) + out = Ref{Ptr{Cvoid}}(C_NULL) + status = C_API.t4a_qtransform_binaryop( + Csize_t(r), Int8(a1), Int8(b1), Int8(a2), Int8(b2), _bc_cint(bc1), _bc_cint(bc2), out) + C_API.check_status(status) + return LinearOperator(out[]) +end + """ cumsum_operator(r::Integer) -> LinearOperator @@ -160,6 +262,40 @@ end # Operator application # ============================================================================ +""" + set_input_space!(op::LinearOperator, state::TreeTensorNetwork) -> LinearOperator + +Reset the operator's true input site indices to match `state`. +""" +function set_input_space!(op::LinearOperator, state::TreeTensorNetwork) + status = C_API.t4a_linop_set_input_space(op.ptr, state.handle) + C_API.check_status(status) + return op +end + +""" + set_output_space!(op::LinearOperator, state::TreeTensorNetwork) -> LinearOperator + +Reset the operator's true output site indices to match `state`. +""" +function set_output_space!(op::LinearOperator, state::TreeTensorNetwork) + status = C_API.t4a_linop_set_output_space(op.ptr, state.handle) + C_API.check_status(status) + return op +end + +""" + set_iospaces!(op::LinearOperator, input_state::TreeTensorNetwork, output_state::TreeTensorNetwork=input_state) -> LinearOperator + +Reset the operator's true input and output site indices to match the given states. +""" +function set_iospaces!(op::LinearOperator, input_state::TreeTensorNetwork, + output_state::TreeTensorNetwork=input_state) + set_input_space!(op, input_state) + set_output_space!(op, output_state) + return op +end + """ apply(op::LinearOperator, state::TreeTensorNetwork; method=:naive, rtol=0.0, maxdim=0) -> TreeTensorNetwork diff --git a/src/Tensor4all.jl b/src/Tensor4all.jl index 649ec10..dc1094a 100644 --- a/src/Tensor4all.jl +++ b/src/Tensor4all.jl @@ -912,6 +912,8 @@ include("TreeTN.jl") # Quantics grid types for coordinate conversions in QTT methods. # Use: using Tensor4all.QuanticsGrids include("QuanticsGrids.jl") +using .QuanticsGrids: DiscretizedGrid, InherentDiscreteGrid, localdimensions +export DiscretizedGrid, InherentDiscreteGrid, localdimensions # ============================================================================ # QuanticsTCI Submodule (Quantics Tensor Cross Interpolation) diff --git a/src/TreeTN.jl b/src/TreeTN.jl index 1d5bffe..0b04edf 100644 --- a/src/TreeTN.jl +++ b/src/TreeTN.jl @@ -28,6 +28,7 @@ using LinearAlgebra import ..Tensor4all: Index, Tensor, dim, id, tags, indices, rank, dims, data import ..Tensor4all: hascommoninds, commoninds, uniqueinds, HasCommonIndsPredicate import ..Tensor4all: C_API +import ..SimpleTT: SimpleTensorTrain, site_tensor # ============================================================================ # CanonicalForm Enum @@ -154,6 +155,50 @@ function MPS(tensors::Vector{Tensor}) return TreeTensorNetwork{Int}(out[], node_map, node_names) end +""" + MPS(tt::SimpleTensorTrain) + +Convert a `SimpleTensorTrain` to an MPS by materializing each site tensor +and attaching fresh site/link indices. +""" +function MPS(tt::SimpleTensorTrain) + n = length(tt) + n == 0 && error("Cannot create MPS from empty SimpleTensorTrain") + + tensors = Tensor[] + links = Index[] + + for i in 1:n + st = site_tensor(tt, i - 1) + left_dim, site_dim, right_dim = size(st) + + inds = Index[] + if i > 1 + push!(inds, links[end]) + end + push!(inds, Index(site_dim)) + if i < n + link = Index(right_dim; tags="Link,l=$i") + push!(links, link) + push!(inds, link) + end + + d = if i == 1 && i == n + reshape(st, site_dim) + elseif i == 1 + reshape(st, site_dim, right_dim) + elseif i == n + reshape(st, left_dim, site_dim) + else + st + end + + push!(tensors, Tensor(inds, d)) + end + + return MPS(tensors) +end + # Note: MPO(tensors::Vector{Tensor}) is not defined separately because # MPO === MPS === TreeTensorNetwork{Int}, so MPS(tensors) works for both. # Defining a separate function would overwrite the MPS constructor. diff --git a/test/runtests.jl b/test/runtests.jl index 97bfcd9..446f00d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,6 +14,8 @@ skip_hdf5 = get(ENV, "T4A_SKIP_HDF5_TESTS", "") == "1" include("test_treetn.jl") include("test_treetci.jl") include("test_simplett.jl") + include("test_quanticsgrids.jl") + include("test_quanticstransform.jl") if !skip_hdf5 include("test_hdf5.jl") end diff --git a/test/test_quanticsgrids.jl b/test/test_quanticsgrids.jl new file mode 100644 index 0000000..5f2644a --- /dev/null +++ b/test/test_quanticsgrids.jl @@ -0,0 +1,22 @@ +using Test +using Tensor4all + +@testset "QuanticsGrids" begin + @testset "top-level exports" begin + @test isdefined(Tensor4all, :DiscretizedGrid) + @test isdefined(Tensor4all, :InherentDiscreteGrid) + @test isdefined(Tensor4all, :localdimensions) + end + + @testset "grouped unfolding" begin + grid = Tensor4all.QuanticsGrids.DiscretizedGrid( + 2, [2, 2], [0.0, 0.0], [1.0, 1.0]; unfolding=:grouped) + + q = Tensor4all.QuanticsGrids.origcoord_to_quantics(grid, [0.25, 0.75]) + x = Tensor4all.QuanticsGrids.quantics_to_origcoord(grid, q) + + @test length(q) == 4 + @test x ≈ [0.25, 0.75] atol=0.3 + @test Tensor4all.QuanticsGrids.localdimensions(grid) == fill(2, 4) + end +end diff --git a/test/test_quanticstransform.jl b/test/test_quanticstransform.jl new file mode 100644 index 0000000..ed83d8a --- /dev/null +++ b/test/test_quanticstransform.jl @@ -0,0 +1,110 @@ +using Test +using Tensor4all +using Tensor4all: dim +using Tensor4all.SimpleTT: SimpleTensorTrain +using Tensor4all.TreeTN: MPS, siteinds +using Tensor4all.QuanticsTransform: + LinearOperator, + affine_operator, + apply, + binaryop_operator, + flip_operator_multivar, + phase_rotation_operator_multivar, + set_iospaces!, + shift_operator, + shift_operator_multivar + +const CAPI = Tensor4all.C_API + +@testset "QuanticsTransform C API bindings" begin + @testset "multivar constructors" begin + out = Ref{Ptr{Cvoid}}(C_NULL) + + status = CAPI.t4a_qtransform_shift_multivar( + Csize_t(4), Int64(1), Cint(0), Csize_t(3), Csize_t(1), out) + @test status == 0 + @test out[] != C_NULL + CAPI.t4a_linop_release(out[]) + + out[] = C_NULL + status = CAPI.t4a_qtransform_flip_multivar( + Csize_t(4), Cint(1), Csize_t(3), Csize_t(2), out) + @test status == 0 + @test out[] != C_NULL + CAPI.t4a_linop_release(out[]) + + out[] = C_NULL + status = CAPI.t4a_qtransform_phase_rotation_multivar( + Csize_t(4), Cdouble(pi / 3), Csize_t(3), Csize_t(0), out) + @test status == 0 + @test out[] != C_NULL + CAPI.t4a_linop_release(out[]) + end + + @testset "affine and binaryop constructors" begin + out = Ref{Ptr{Cvoid}}(C_NULL) + + a_num = Int64[1, 1, 0, 0, 1, 1] + a_den = fill(Int64(1), 6) + b_num = Int64[0, 0, 0] + b_den = fill(Int64(1), 3) + bc = Cint[1, 1, 0] + + status = CAPI.t4a_qtransform_affine( + Csize_t(4), a_num, a_den, b_num, b_den, Csize_t(3), Csize_t(2), bc, out) + @test status == 0 + @test out[] != C_NULL + CAPI.t4a_linop_release(out[]) + + out[] = C_NULL + status = CAPI.t4a_qtransform_binaryop( + Csize_t(4), Int8(1), Int8(1), Int8(1), Int8(-1), Cint(1), Cint(0), out) + @test status == 0 + @test out[] != C_NULL + CAPI.t4a_linop_release(out[]) + end + + @testset "mapping rewrite enables high-level apply" begin + tt = SimpleTensorTrain([2, 2, 2], 1.0) + mps = MPS(tt) + op = shift_operator(3, 1) + + set_iospaces!(op, mps) + result = apply(op, mps; method=:naive) + + @test result isa Tensor4all.TreeTN.TreeTensorNetwork + end + + @testset "high-level multivar wrappers construct operators" begin + @test shift_operator_multivar(3, 1, 2, 0) isa LinearOperator + @test flip_operator_multivar(3, 2, 1; bc=Tensor4all.QuanticsTransform.Open) isa LinearOperator + @test phase_rotation_operator_multivar(3, pi / 4, 2, 1) isa LinearOperator + @test binaryop_operator(3, 1, 1, 1, -1) isa LinearOperator + end + + @testset "affine wrapper supports explicit output space" begin + input_mps = MPS(SimpleTensorTrain(fill(4, 3), 1.0)) + output_mps = MPS(SimpleTensorTrain(fill(8, 3), 0.0)) + + a_num = Int64[ + 1 -1 + 1 0 + 0 1 + ] + a_den = ones(Int64, 3, 2) + b_num = Int64[0, 0, 0] + b_den = ones(Int64, 3) + bc = [ + Tensor4all.QuanticsTransform.Open, + Tensor4all.QuanticsTransform.Periodic, + Tensor4all.QuanticsTransform.Periodic, + ] + + op = affine_operator(3, a_num, a_den, b_num, b_den; bc=bc) + set_iospaces!(op, input_mps, output_mps) + result = apply(op, input_mps; method=:naive) + + @test result isa Tensor4all.TreeTN.TreeTensorNetwork + @test dim(siteinds(result, 1)[1]) == 8 + end +end From 5baed27a3aec890174d1aa38a518ac978f66ae03 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Mon, 6 Apr 2026 19:14:48 +0900 Subject: [PATCH 2/2] pin fallback build to quantics transform C API --- deps/build.jl | 2 +- test/test_build_script.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/build.jl b/deps/build.jl index ece2851..862c66d 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -12,7 +12,7 @@ using Libdl using RustToolChain: cargo # Configuration -const TENSOR4ALL_RS_FALLBACK_COMMIT = "8a68db84d517286ee55fda3585eebb26667e200a" +const TENSOR4ALL_RS_FALLBACK_COMMIT = "44ddedb3ff801f80c2f8d1609bf43eb28081a9f1" const TENSOR4ALL_RS_REPO = "https://github.com/tensor4all/tensor4all-rs.git" # Paths diff --git a/test/test_build_script.jl b/test/test_build_script.jl index 6e98a6e..eaaad1c 100644 --- a/test/test_build_script.jl +++ b/test/test_build_script.jl @@ -3,7 +3,7 @@ using Test @testset "build.jl" begin script = read(joinpath(dirname(@__DIR__), "deps", "build.jl"), String) - @test occursin(r"const TENSOR4ALL_RS_FALLBACK_COMMIT = \"[0-9a-f]{40}\"", script) + @test occursin("const TENSOR4ALL_RS_FALLBACK_COMMIT = \"44ddedb3ff801f80c2f8d1609bf43eb28081a9f1\"", script) @test occursin("checkout --detach", script) @test !occursin("--branch", script) end