From 6a8597dd326abdca76aca234933bb68882e3af68 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 7 Apr 2026 19:08:15 +0900 Subject: [PATCH 1/5] docs: add Documenter.jl setup with GitHub Pages deployment - AGENTS.md: add Error Handling and Documentation Requirements sections - docs/make.jl: Documenter.jl build with per-module API pages - docs/src/: index, module architecture, 7 API reference pages, tutorial stubs - .github/workflows/docs.yml: CI builds Rust lib, generates docs, deploys to gh-pages Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/docs.yml | 44 ++++++++++++ AGENTS.md | 113 ++++++++++++++++++++++++++++++ docs/Project.toml | 6 ++ docs/make.jl | 36 ++++++++++ docs/src/api/core.md | 56 +++++++++++++++ docs/src/api/quanticsgrids.md | 6 ++ docs/src/api/quanticstci.md | 6 ++ docs/src/api/quanticstransform.md | 6 ++ docs/src/api/simplett.md | 6 ++ docs/src/api/treetci.md | 6 ++ docs/src/api/treetn.md | 6 ++ docs/src/index.md | 47 +++++++++++++ docs/src/modules.md | 78 +++++++++++++++++++++ docs/src/tutorials/fourier1d.md | 4 ++ docs/src/tutorials/quantics1d.md | 4 ++ 15 files changed, 424 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/api/core.md create mode 100644 docs/src/api/quanticsgrids.md create mode 100644 docs/src/api/quanticstci.md create mode 100644 docs/src/api/quanticstransform.md create mode 100644 docs/src/api/simplett.md create mode 100644 docs/src/api/treetci.md create mode 100644 docs/src/api/treetn.md create mode 100644 docs/src/index.md create mode 100644 docs/src/modules.md create mode 100644 docs/src/tutorials/fourier1d.md create mode 100644 docs/src/tutorials/quantics1d.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..14a9004 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,44 @@ +name: Documentation + +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + +permissions: + contents: write + statuses: write + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + docs: + name: Build and Deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: julia-actions/setup-julia@v2 + with: + version: '1.11' + + - uses: julia-actions/cache@v2 + + - name: Install package and build Rust library + run: | + julia --project=docs -e ' + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate() + Pkg.build("Tensor4all") + ' + + - name: Build and deploy documentation + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + run: julia --project=docs docs/make.jl diff --git a/AGENTS.md b/AGENTS.md index 86a9b66..d4e5980 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,117 @@ # AGENTS.md +## API Design Principles + +Tensor4all.jl wraps tensor4all-rs (Rust) via C FFI. The API is layered: + +### C API layer (tensor4all-capi in Rust) +- **General, multi-language primitives only.** If Python/C++ would also need it, put it here. +- Examples: tensor contraction, TreeTN operations, index manipulation, TCI, quantics transforms +- Do NOT add chain-specific or application-specific operations + +### Julia layer (this package) +- **TreeTN-general.** Design APIs for arbitrary tree tensor networks, not just chains. +- Chain-specific operations must include **runtime topology checks** (verify the TreeTN is a chain before proceeding). +- **Build on C API primitives.** If an operation can be implemented using `ttn[v]` get/set + tensor contraction + index operations, implement it in Julia — do not add a new C API function. +- Expose Rust functionality that isn't in C API yet only when it's general enough for multi-language use. + +### Type system: TensorTrain as the primary chain type +`TensorTrain = TreeTensorNetwork{Int}` is the primary type for chain tensor networks. `MPS` and `MPO` are aliases for the same type — there is no type-level distinction. +- MPS-like: each vertex has 1 site index +- MPO-like: each vertex has 2 site indices (e.g., unprimed input + primed output) +- `is_chain(ttn)`: runtime check that topology is a linear chain with vertices 1, 2, ..., n +- `is_mps_like(tt)`: runtime check that each vertex has exactly 1 site index +- `is_mpo_like(tt)`: runtime check that each vertex has exactly 2 site indices (unprimed + primed pair) +- Operations that need a specific index structure check at runtime via these predicates. + +### Key available C API primitives for building Julia-level operations +- `ttn[v]` / `ttn[v] = tensor` — get/set individual tensors at vertices +- `siteinds(ttn, v)` — site indices at a vertex +- `linkind(ttn, v1, v2)` — bond index between vertices +- `vertices(ttn)`, `neighbors(ttn, v)` — topology queries +- `contract(a, b)` — TreeTN-TreeTN contraction +- `orthogonalize!`, `truncate!`, `inner`, `norm`, `to_dense`, `evaluate` + +### Known missing C API (should be added to tensor4all-rs) +- **Tensor-Tensor contraction**: Exists in Rust (`TensorDynLen::contract`) but not exposed in C API. This is fundamental and multi-language useful. + +### Julia-level API to implement (using C API primitives) +These should NOT go in the C API — they can be built from existing primitives once Tensor-Tensor contraction is exposed: +- **Site summation**: Contract specific site indices on a TreeTN. Use `ttn[v]` + tensor contraction with sum vectors. +- **Tensor diagonal embedding**: Create tensor diagonal in specific indices. Use Julia array operations + Tensor constructor. +- **Partial site index replacement**: Replace specific site indices in TreeTN. Use `ttn[v]` get/set + index operations. +- **Adding/removing site indices at a vertex**: For "MPO-like" structures — manipulate the tensor at a vertex to add primed copies of site indices. This is NOT a type conversion (MPS === MPO), just tensor manipulation. + +## Error Handling + +### Principle: fail early with actionable messages +Users should never see a raw Rust panic or an opaque "Internal error". Errors must explain **what went wrong** and **how to fix it**. + +### Julia-side validation (prefer) +Validate arguments in Julia **before** calling the C API. This produces familiar Julia stack traces and clear messages. +- Check types, dimensions, and index compatibility before C calls +- Use `ArgumentError` for invalid arguments, `DimensionMismatch` for shape mismatches +- Include the actual vs expected values in the message: `"expected MPS with $n sites, got $(nv(ttn))"` +- Runtime topology checks (`is_chain`, `is_mps_like`, `is_mpo_like`) must produce descriptive errors, not just `false` + +### Array contiguity check +Julia arrays passed to the C API via pointer must be **contiguous in memory** (dense, column-major). Views, reshapes of non-contiguous data, `SubArray`, and `PermutedDimsArray` are NOT safe to pass directly. +- Before any `ccall` that takes a `Ptr{T}` to array data, verify contiguity or convert: `data = collect(data)` if needed. +- Use `Base.iscontiguous(A)` (Julia ≥ 1.11) or `stride(A,1) == 1 && strides match dense layout` to check. +- Error message: `"Array must be contiguous in memory for C API. Got $(typeof(data)) with strides $(strides(data)). Use collect() to make a contiguous copy."` + +### Rust-side errors (propagate fully) +When a C API call fails, `check_status` already retrieves `last_error_message()` from Rust. Rules: +- **Never discard the Rust error message.** Always include it in the Julia exception. +- If wrapping a C API call in a higher-level Julia function, add context: `"Failed to contract TTNs: $rust_msg"` +- Rust error messages should describe the problem from the user's perspective, not internal Rust state. + +### Examples of good vs bad error messages +``` +# Bad +error("C API error: Internal error") +error("Null pointer error") + +# Good +error("Cannot apply Fourier operator: input MPS has $(nv(mps)) sites but operator expects $r sites") +error("contract failed: input and operator have no common site indices. Did you call set_iospaces!() first?") +``` + +## Documentation Requirements + +### Docstring rules +- Every exported type and function **must** have a docstring with a `# Examples` section. +- Examples should be runnable `jldoctest` blocks where possible. Use `julia> ` prompts for testable examples. +- For examples that require the Rust library or external state, use ` ```julia ... ``` ` fenced blocks (non-tested). +- Module-level docstrings (`@doc` at the top of each module file) should include an end-to-end usage overview. + +### Documenter.jl site +- Documentation is built with [Documenter.jl](https://documenter.juliadocs.org/) under `docs/`. +- `docs/make.jl` generates the site; `docs/src/` contains manual pages. +- PR checklist includes `julia --project=docs docs/make.jl` passing without errors. + +### Documentation structure +- `docs/src/index.md` — overview, module dependency graph, and quick start +- `docs/src/api.md` — auto-generated API reference via `@autodocs` (per-module sections) +- `docs/src/modules.md` — module overview with dependency graph and data flow between modules +- `docs/src/tutorials/` — pipeline-oriented guides that chain multiple modules: + - Quantics interpolation: QuanticsGrids → QuanticsTCI → SimpleTT → MPS + - Fourier analysis: QuanticsTCI → MPS → QuanticsTransform(fourier) → evaluate + - Affine transform on MPS: TreeTN → QuanticsTransform(affine) → TreeTN + - Tree TCI: TreeTCI → TreeTensorNetwork → operations + +### Module dependency graph +``` +Core: Index, Tensor, Algorithm (foundation types) + ↓ +SimpleTT ←→ TreeTN (bidirectional conversion) + ↓ ↓ + QuanticsGrids QuanticsTransform (operators on TreeTN) + ↓ + QuanticsTCI TreeTCI (outputs TreeTN) +``` +Typical pipeline: **QuanticsGrids** (grid定義) → **QuanticsTCI** (関数補間→SimpleTT) → **TreeTN** (MPS変換・操作) → **QuanticsTransform** (Fourier/shift/affine演算) → **TreeTN** (結果評価) + ## Known Issues ### Julia x64 segfault on AMD EPYC (primerose) @@ -44,3 +156,4 @@ As of commit `ca97593` (PR #302), tensor4all-rs uses **column-major** storage fo ## 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. +- Run `julia --project=docs docs/make.jl` and verify documentation builds without errors or missing docstring warnings. diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..95a1cc9 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,6 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Tensor4all = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + +[compat] +Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..a499662 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,36 @@ +using Documenter +using Tensor4all + +makedocs( + sitename="Tensor4all.jl", + modules=[Tensor4all], + repo=Documenter.Remotes.GitHub("tensor4all", "Tensor4all.jl"), + pages=[ + "Home" => "index.md", + "Modules" => "modules.md", + "API Reference" => [ + "Core (Index, Tensor)" => "api/core.md", + "SimpleTT" => "api/simplett.md", + "TreeTN (MPS/MPO)" => "api/treetn.md", + "QuanticsGrids" => "api/quanticsgrids.md", + "QuanticsTCI" => "api/quanticstci.md", + "QuanticsTransform" => "api/quanticstransform.md", + "TreeTCI" => "api/treetci.md", + ], + "Tutorials" => [ + "1D Quantics Interpolation" => "tutorials/quantics1d.md", + "1D Fourier Transform" => "tutorials/fourier1d.md", + ], + ], + format=Documenter.HTML( + prettyurls=get(ENV, "CI", "false") == "true", + canonical="https://tensor4all.github.io/Tensor4all.jl", + ), + warnonly=[:missing_docs], +) + +deploydocs( + repo="github.com/tensor4all/Tensor4all.jl.git", + devbranch="main", + push_preview=true, +) diff --git a/docs/src/api/core.md b/docs/src/api/core.md new file mode 100644 index 0000000..964e1a2 --- /dev/null +++ b/docs/src/api/core.md @@ -0,0 +1,56 @@ +# Core: Index, Tensor, Algorithm + +## Index + +```@docs +Index +dim +tags +id +hastag +plev +sim +prime +noprime +setprime +hasind +hasinds +hascommoninds +commoninds +commonind +common_inds +uniqueinds +uniqueind +noncommoninds +replaceinds +replaceind +onehot +``` + +## Tensor + +```@docs +Tensor +rank +dims +indices +data +storage_kind +contract +diag_embed +diag_trace +``` + +## Storage Kinds + +```@docs +StorageKind +``` + +## Algorithm + +```@docs +Algorithm +get_default_svd_rtol +resolve_truncation_tolerance +``` diff --git a/docs/src/api/quanticsgrids.md b/docs/src/api/quanticsgrids.md new file mode 100644 index 0000000..9c21415 --- /dev/null +++ b/docs/src/api/quanticsgrids.md @@ -0,0 +1,6 @@ +# QuanticsGrids + +```@autodocs +Modules = [Tensor4all.QuanticsGrids] +Order = [:type, :function] +``` diff --git a/docs/src/api/quanticstci.md b/docs/src/api/quanticstci.md new file mode 100644 index 0000000..0597585 --- /dev/null +++ b/docs/src/api/quanticstci.md @@ -0,0 +1,6 @@ +# QuanticsTCI + +```@autodocs +Modules = [Tensor4all.QuanticsTCI] +Order = [:type, :function] +``` diff --git a/docs/src/api/quanticstransform.md b/docs/src/api/quanticstransform.md new file mode 100644 index 0000000..f1c04c4 --- /dev/null +++ b/docs/src/api/quanticstransform.md @@ -0,0 +1,6 @@ +# QuanticsTransform + +```@autodocs +Modules = [Tensor4all.QuanticsTransform] +Order = [:type, :function] +``` diff --git a/docs/src/api/simplett.md b/docs/src/api/simplett.md new file mode 100644 index 0000000..c95c537 --- /dev/null +++ b/docs/src/api/simplett.md @@ -0,0 +1,6 @@ +# SimpleTT + +```@autodocs +Modules = [Tensor4all.SimpleTT] +Order = [:type, :function] +``` diff --git a/docs/src/api/treetci.md b/docs/src/api/treetci.md new file mode 100644 index 0000000..c9b3513 --- /dev/null +++ b/docs/src/api/treetci.md @@ -0,0 +1,6 @@ +# TreeTCI + +```@autodocs +Modules = [Tensor4all.TreeTCI] +Order = [:type, :function] +``` diff --git a/docs/src/api/treetn.md b/docs/src/api/treetn.md new file mode 100644 index 0000000..5382041 --- /dev/null +++ b/docs/src/api/treetn.md @@ -0,0 +1,6 @@ +# TreeTN (MPS / MPO / Tree Tensor Networks) + +```@autodocs +Modules = [Tensor4all.TreeTN] +Order = [:type, :function] +``` diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..a037b9c --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,47 @@ +# Tensor4all.jl + +A Julia wrapper for [tensor4all-rs](https://github.com/tensor4all/tensor4all-rs), providing tensor network operations via C FFI. + +## Features + +- **Index & Tensor**: Named indices with tags, prime levels, and automatic contraction +- **SimpleTT**: Simple tensor trains with fixed site dimensions +- **TreeTN (MPS/MPO)**: Tree tensor networks — MPS, MPO, and general tree topologies +- **QuanticsGrids**: Coordinate transforms between physical grids and quantics (binary) representation +- **QuanticsTCI**: Tensor cross interpolation in quantics representation +- **QuanticsTransform**: Fourier, shift, flip, phase rotation, and affine operators on MPS +- **TreeTCI**: TCI on arbitrary tree topologies + +## Quick Start + +```julia +using Tensor4all +using Tensor4all.QuanticsGrids +using Tensor4all.QuanticsTCI +using Tensor4all.TreeTN + +# Interpolate a function on a quantics grid +R = 20 # 2^20 grid points +grid = DiscretizedGrid{1}(R, 0.0, 1.0) +f(x) = exp(-10x) * cos(100x) +ci, ranks, errors = quanticscrossinterpolate(Float64, f, grid; tolerance=1e-8) + +# Convert to MPS for further operations +tt = to_tensor_train(ci) +mps = MPS(tt) +``` + +See the tutorials section in the sidebar for cross-module workflows. + +## Installation + +```julia +using Pkg +Pkg.add(url="https://github.com/tensor4all/Tensor4all.jl") +``` + +The Rust backend (`libtensor4all_capi`) is built automatically during `Pkg.build()`. + +## Module Overview + +See [Module Architecture](@ref) for the dependency graph and data flow. diff --git a/docs/src/modules.md b/docs/src/modules.md new file mode 100644 index 0000000..dee87c4 --- /dev/null +++ b/docs/src/modules.md @@ -0,0 +1,78 @@ +# [Module Architecture](@id Module-Architecture) + +## Dependency Graph + +``` + ┌──────────────────────┐ + │ Core Foundation │ + │ Index, Tensor, │ + │ Algorithm │ + └──────────┬───────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────────┐ + │ SimpleTT │◄─►│ TreeTN │ │QuanticsGrids │ + │ │ │ MPS/MPO │ │ │ + └──────────┘ └────┬─────┘ └──────┬───────┘ + │ │ + ┌────────┼────────┐ │ + ▼ ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌────────────┐ + │Quantics │ │Tree │ │QuanticsTCI │ + │Transform │ │TCI │ │ │ + └──────────┘ └──────┘ └────────────┘ +``` + +## Data Flow + +Tensor4all.jl modules form a pipeline where data flows from grid definition through interpolation to tensor network operations: + +| Stage | Module | Input | Output | +|-------|--------|-------|--------| +| Grid definition | **QuanticsGrids** | Domain bounds, R (bits) | `DiscretizedGrid`, `InherentDiscreteGrid` | +| Function interpolation | **QuanticsTCI** | Function + Grid | `QuanticsTensorCI2` | +| Simple tensor train | **SimpleTT** | TCI result via `to_tensor_train()` | `SimpleTensorTrain` | +| Tensor network | **TreeTN** | SimpleTT via `MPS()` | `MPS` / `TreeTensorNetwork` | +| Operators | **QuanticsTransform** | MPS + operator | Transformed `MPS` | +| Tree interpolation | **TreeTCI** | Function + tree graph | `TreeTensorNetwork` | + +## Typical Pipelines + +### Quantics function interpolation +``` +QuanticsGrids.DiscretizedGrid → QuanticsTCI.quanticscrossinterpolate + → QuanticsTCI.to_tensor_train → SimpleTensorTrain + → TreeTN.MPS → MPS operations (truncate!, orthogonalize!, inner, contract) +``` + +### Fourier analysis of interpolated function +``` +QuanticsTCI result → MPS + → QuanticsTransform.fourier_operator → apply → Fourier-space MPS + → TreeTCI.evaluate (at specific k-points) +``` + +### Affine coordinate transform +``` +MPS → QuanticsTransform.affine_pullback_operator → apply → transformed MPS +``` + +### Tree-structured interpolation +``` +TreeTCI.crossinterpolate2 → TreeTensorNetwork + → TreeTN operations (contract, truncate!, evaluate) +``` + +## Type Relationships + +```julia +# Chain tensor networks +TensorTrain = TreeTensorNetwork{Int} # Primary chain type +MPS = TensorTrain # Alias (no type distinction) +MPO = TensorTrain # Alias (no type distinction) + +# MPS-like vs MPO-like is a runtime property: +is_mps_like(tt) # each vertex has 1 site index +is_mpo_like(tt) # each vertex has 2 site indices (unprimed + primed) +``` diff --git a/docs/src/tutorials/fourier1d.md b/docs/src/tutorials/fourier1d.md new file mode 100644 index 0000000..4cccd86 --- /dev/null +++ b/docs/src/tutorials/fourier1d.md @@ -0,0 +1,4 @@ +# 1D Fourier Transform + +!!! note "Work in progress" + This tutorial is under construction. See [README](https://github.com/tensor4all/Tensor4all.jl) for usage examples. diff --git a/docs/src/tutorials/quantics1d.md b/docs/src/tutorials/quantics1d.md new file mode 100644 index 0000000..e2d9488 --- /dev/null +++ b/docs/src/tutorials/quantics1d.md @@ -0,0 +1,4 @@ +# 1D Quantics Interpolation + +!!! note "Work in progress" + This tutorial is under construction. See [README](https://github.com/tensor4all/Tensor4all.jl) for usage examples. From 10043c206e49a49056331d0ce97c4a02b1978f1d Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 7 Apr 2026 19:21:22 +0900 Subject: [PATCH 2/5] fix: sync QuanticsTCI ccall signatures with tensor4all-rs C API The Rust C API (PR #393) expanded t4a_quanticscrossinterpolate_f64 and t4a_quanticscrossinterpolate_discrete_f64 from 7/9 to 13/15 parameters, adding options, initial_pivots, n_pivots, out_ranks, out_errors, and out_n_iters. The Julia ccall bindings were not updated, causing stack misalignment and a capacity overflow panic in parse_initial_pivots. - C_API.jl: update both ccall bindings to match 13/15-parameter signatures - QuanticsTCI.jl: allocate rank/error buffers, return (qtci, ranks, errors) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/C_API.jl | 36 ++++++++++++++++++++++++++++-------- src/QuanticsTCI.jl | 32 ++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/C_API.jl b/src/C_API.jl index 6863789..a34af3f 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -2067,7 +2067,9 @@ end # ============================================================================ """ - t4a_quanticscrossinterpolate_f64(grid, eval_fn, user_data, tolerance, max_bonddim, max_iter, out_qtci) -> Cint + t4a_quanticscrossinterpolate_f64(grid, eval_fn, user_data, options, tolerance, + max_bonddim, max_iter, initial_pivots, n_pivots, out_qtci, + out_ranks, out_errors, out_n_iters) -> Cint Continuous domain interpolation using a DiscretizedGrid. """ @@ -2075,21 +2077,31 @@ function t4a_quanticscrossinterpolate_f64( grid::Ptr{Cvoid}, eval_fn::Ptr{Cvoid}, user_data::Ptr{Cvoid}, + options::Ptr{Cvoid}, tolerance::Cdouble, max_bonddim::Csize_t, max_iter::Csize_t, - out_qtci::Ref{Ptr{Cvoid}}, + initial_pivots, + n_pivots::Csize_t, + out_qtci, + out_ranks, + out_errors, + out_n_iters, ) return ccall( _sym(:t4a_quanticscrossinterpolate_f64), Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Cdouble, Csize_t, Csize_t, Ptr{Ptr{Cvoid}}), - grid, eval_fn, user_data, tolerance, max_bonddim, max_iter, out_qtci + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Cdouble, Csize_t, Csize_t, + Ptr{Int64}, Csize_t, Ptr{Ptr{Cvoid}}, Ptr{Csize_t}, Ptr{Cdouble}, Ptr{Csize_t}), + grid, eval_fn, user_data, options, tolerance, max_bonddim, max_iter, + initial_pivots, n_pivots, out_qtci, out_ranks, out_errors, out_n_iters ) end """ - t4a_quanticscrossinterpolate_discrete_f64(sizes, ndims, eval_fn, user_data, tolerance, max_bonddim, max_iter, unfoldingscheme, out_qtci) -> Cint + t4a_quanticscrossinterpolate_discrete_f64(sizes, ndims, eval_fn, user_data, options, + tolerance, max_bonddim, max_iter, unfoldingscheme, initial_pivots, n_pivots, + out_qtci, out_ranks, out_errors, out_n_iters) -> Cint Discrete domain interpolation with integer indices. """ @@ -2098,17 +2110,25 @@ function t4a_quanticscrossinterpolate_discrete_f64( ndims::Csize_t, eval_fn::Ptr{Cvoid}, user_data::Ptr{Cvoid}, + options::Ptr{Cvoid}, tolerance::Cdouble, max_bonddim::Csize_t, max_iter::Csize_t, unfoldingscheme::Cint, - out_qtci::Ref{Ptr{Cvoid}}, + initial_pivots, + n_pivots::Csize_t, + out_qtci, + out_ranks, + out_errors, + out_n_iters, ) return ccall( _sym(:t4a_quanticscrossinterpolate_discrete_f64), Cint, - (Ptr{Csize_t}, Csize_t, Ptr{Cvoid}, Ptr{Cvoid}, Cdouble, Csize_t, Csize_t, Cint, Ptr{Ptr{Cvoid}}), - sizes, ndims, eval_fn, user_data, tolerance, max_bonddim, max_iter, unfoldingscheme, out_qtci + (Ptr{Csize_t}, Csize_t, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Cdouble, Csize_t, Csize_t, Cint, + Ptr{Int64}, Csize_t, Ptr{Ptr{Cvoid}}, Ptr{Csize_t}, Ptr{Cdouble}, Ptr{Csize_t}), + sizes, ndims, eval_fn, user_data, options, tolerance, max_bonddim, max_iter, unfoldingscheme, + initial_pivots, n_pivots, out_qtci, out_ranks, out_errors, out_n_iters ) end diff --git a/src/QuanticsTCI.jl b/src/QuanticsTCI.jl index f19aed0..acd356b 100644 --- a/src/QuanticsTCI.jl +++ b/src/QuanticsTCI.jl @@ -257,8 +257,11 @@ function quanticscrossinterpolate( ) f_ref = Ref{Any}(f) out_qtci = Ref{Ptr{Cvoid}}(C_NULL) + out_ranks = Vector{Csize_t}(undef, max_iter) + out_errors = Vector{Cdouble}(undef, max_iter) + out_n_iters = Ref{Csize_t}(0) - GC.@preserve f_ref begin + GC.@preserve f_ref out_ranks out_errors begin user_data = pointer_from_objref(f_ref) trampoline_ptr = _get_trampoline_f64_ptr() @@ -266,16 +269,25 @@ function quanticscrossinterpolate( grid.ptr, trampoline_ptr, user_data, + C_NULL, # options (NULL = use legacy tolerance/max_bonddim/max_iter) tolerance, Csize_t(max_bonddim), Csize_t(max_iter), + C_NULL, # initial_pivots + Csize_t(0), # n_pivots out_qtci, + pointer(out_ranks), + pointer(out_errors), + out_n_iters, ) C_API.check_status(status) end - return QuanticsTensorCI2(out_qtci[]) + n = Int(out_n_iters[]) + ranks = Int.(out_ranks[1:n]) + errors = Float64.(out_errors[1:n]) + return QuanticsTensorCI2(out_qtci[]), ranks, errors end """ @@ -314,8 +326,11 @@ function quanticscrossinterpolate_discrete( f_ref = Ref{Any}(f) out_qtci = Ref{Ptr{Cvoid}}(C_NULL) sizes_c = Csize_t.(sizes) + out_ranks = Vector{Csize_t}(undef, max_iter) + out_errors = Vector{Cdouble}(undef, max_iter) + out_n_iters = Ref{Csize_t}(0) - GC.@preserve f_ref begin + GC.@preserve f_ref out_ranks out_errors begin user_data = pointer_from_objref(f_ref) trampoline_ptr = _get_trampoline_i64_ptr() @@ -324,17 +339,26 @@ function quanticscrossinterpolate_discrete( Csize_t(length(sizes)), trampoline_ptr, user_data, + C_NULL, # options tolerance, Csize_t(max_bonddim), Csize_t(max_iter), scheme, + C_NULL, # initial_pivots + Csize_t(0), # n_pivots out_qtci, + pointer(out_ranks), + pointer(out_errors), + out_n_iters, ) C_API.check_status(status) end - return QuanticsTensorCI2(out_qtci[]) + n = Int(out_n_iters[]) + ranks = Int.(out_ranks[1:n]) + errors = Float64.(out_errors[1:n]) + return QuanticsTensorCI2(out_qtci[]), ranks, errors end end # module QuanticsTCI From ee1fd2ef0074c6133508f2170292375dbf1380aa Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 7 Apr 2026 19:25:29 +0900 Subject: [PATCH 3/5] docs: add 1D quantics interpolation tutorial Covers grid creation, QTCI with both max_bonddim and tolerance modes, evaluation, integration, bond dimension inspection, and MPS conversion. Uses multi-scale oscillatory function f(x)=exp(-x)*cos(x/B) as main example. Migrated from T4APlutoExamples/quantics1d.jl to Tensor4all.jl API. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/tutorials/quantics1d.md | 256 ++++++++++++++++++++++++++++++- 1 file changed, 254 insertions(+), 2 deletions(-) diff --git a/docs/src/tutorials/quantics1d.md b/docs/src/tutorials/quantics1d.md index e2d9488..b6f7259 100644 --- a/docs/src/tutorials/quantics1d.md +++ b/docs/src/tutorials/quantics1d.md @@ -1,4 +1,256 @@ # 1D Quantics Interpolation -!!! note "Work in progress" - This tutorial is under construction. See [README](https://github.com/tensor4all/Tensor4all.jl) for usage examples. +This tutorial demonstrates how to use Tensor4all.jl to approximate univariate +functions in the **Quantics Tensor Train (QTT)** representation via +**Tensor Cross Interpolation (TCI)**. + +## What is Quantics (QTT) representation? + +The quantics representation encodes a function defined on a grid of ``2^R`` +points as a tensor train with ``R`` sites, each having local dimension 2 +(one qubit). The key idea is to write the grid index in binary and assign +each bit to a separate tensor site. Because many physically relevant +functions exhibit multi-scale structure that translates into low-rank +structure in the quantics representation, the resulting tensor train has +small bond dimension -- often logarithmic in the number of grid points. + +This makes QTT-based algorithms extremely efficient for functions with +features at widely separated scales: oscillations, sharp peaks, slow +envelopes, and combinations thereof. + +## Setup + +```julia +using Tensor4all.QuanticsGrids # Grid types and coordinate conversions +using Tensor4all.QuanticsTCI # quanticscrossinterpolate, evaluate, integral, ... +using Tensor4all.TreeTN # MPS, orthogonalize!, truncate!, ... +``` + +## Warm-up: approximating sin(x) + +We start with a simple example to illustrate the basic workflow. + +### Create a quantics grid + +`DiscretizedGrid` maps a continuous interval onto a uniform grid of ``2^R`` +points. The first argument is the number of dimensions (1 for a univariate +function), the second is the resolution ``R``, and the third/fourth are +vectors giving the lower and upper bounds. + +```julia +R = 20 # 2^20 ≈ 1 million grid points +grid = DiscretizedGrid(1, R, [0.0], [2*pi]) +``` + +### Run QTCI + +`quanticscrossinterpolate` takes the grid and a callable as its first two +positional arguments. The function `f` receives one `Float64` argument per +dimension and must return a `Float64`. + +```julia +f_sin(x) = sin(x) + +ci, ranks, errors = quanticscrossinterpolate(grid, f_sin; tolerance=1e-10) +``` + +The return values are: + +| Value | Description | +|----------|-------------| +| `ci` | `QuanticsTensorCI2` object -- the interpolant | +| `ranks` | `Vector{Int}` -- maximum bond dimension per TCI sweep | +| `errors` | `Vector{Float64}` -- estimated error per sweep | + +### Evaluate the interpolant + +`evaluate` takes a vector of **1-based grid indices** (one per dimension): + +```julia +i = 42 +val = evaluate(ci, [i]) # value of the interpolant at grid point i +``` + +To convert between grid indices and physical coordinates, use the +coordinate conversion functions from `QuanticsGrids`: + +```julia +x_vec = grididx_to_origcoord(grid, [i]) # grid index → coordinate vector +x = x_vec[1] # extract the scalar + +idx_vec = origcoord_to_grididx(grid, [1.0]) # coordinate → grid index vector +``` + +Putting it together to check the approximation quality: + +```julia +for i in [1, 100, 2^R] + x = grididx_to_origcoord(grid, [i])[1] + println("x = $x, QTCI = $(evaluate(ci, [i])), exact = $(f_sin(x))") +end +``` + +### Compute the integral + +`integral(ci)` returns the integral over the domain (the sum of all grid +values multiplied by the grid spacing): + +```julia +I_qtci = integral(ci) +I_exact = -cos(2*pi) + cos(0.0) # = 0 +println("QTCI integral: $I_qtci, exact: $I_exact") +``` + +`sum(ci)` returns the plain sum of all ``2^R`` function values (without the +grid-spacing factor): + +```julia +S = sum(ci) +``` + +The two are related by `integral(ci) ≈ sum(ci) * dx` where +`dx = (upper - lower) / 2^R`. + +### Inspect bond dimensions + +```julia +println("Maximum bond dimension: ", rank(ci)) +println("Bond dimensions: ", link_dims(ci)) +``` + +For `sin(x)` with tolerance `1e-10`, the maximum bond dimension will +typically be quite small (around 5--10), reflecting the low complexity of +the function in the quantics representation. + +## Main example: multi-scale oscillatory function + +Now consider a function with structure at vastly different scales -- the +kind of problem where QTT really shines: + +```math +f(x) = e^{-x}\,\cos\!\Bigl(\frac{x}{B}\Bigr), \qquad B = 2^{-30}. +``` + +The cosine oscillates on a scale of order ``B \approx 10^{-9}`` while the +envelope ``e^{-x}`` varies on a scale of order 1. Resolving both scales +on a uniform grid requires ``\sim 2^{40}`` points -- over a trillion -- +yet the QTT representation needs only modest bond dimension. + +### Define the function and grid + +```julia +B = 2^(-30) +f(x) = exp(-x) * cos(x / B) + +R = 40 +grid = DiscretizedGrid(1, R, [0.0], [1.0]) +``` + +### Run QTCI with a bond-dimension cap + +You can limit the maximum bond dimension instead of (or in addition to) +specifying a tolerance: + +```julia +ci, ranks, errors = quanticscrossinterpolate(grid, f; max_bonddim=15) +``` + +Or use a tolerance-based stopping criterion: + +```julia +ci2, ranks2, errors2 = quanticscrossinterpolate(grid, f; tolerance=1e-8) +``` + +!!! tip + When you are unsure how large the bond dimension needs to be, start with + a tolerance-based run. Use `max_bonddim` when you need to control + computational cost or memory directly. + +### Check the approximation + +```julia +for i in [1, 2, 3, 2^R] + x = grididx_to_origcoord(grid, [i])[1] + println("x = $x, QTCI = $(evaluate(ci, [i])), exact = $(f(x))") +end +``` + +### Coordinate conversions + +`origcoord_to_grididx` maps a physical coordinate back to the nearest grid +index. This is useful for probing the interpolant at a specific point in +the domain: + +```julia +x_query = 0.5 +i_vec = origcoord_to_grididx(grid, [x_query]) +val = evaluate(ci, i_vec) +println("f($x_query) ≈ $val") +``` + +### Compute the integral + +```julia +I_qtci = integral(ci) +println("Integral via QTCI: $I_qtci") +``` + +### Inspect the TCI convergence + +The `ranks` and `errors` vectors track the TCI convergence across sweeps. +They can be used to verify that the interpolation has converged: + +```julia +println("Ranks per sweep: ", ranks) +println("Errors per sweep: ", errors) +println("Final bond dimension: ", rank(ci)) +println("Bond dimensions: ", link_dims(ci)) +``` + +## Converting to MPS + +The QTCI result can be converted to an `MPS` (a `TreeTensorNetwork{Int}`) +for further tensor-network operations such as orthogonalization, truncation, +contraction with other MPS/MPO, etc. + +The conversion is a two-step process: first extract a `SimpleTensorTrain`, +then wrap it as an `MPS`: + +```julia +tt = to_tensor_train(ci) # SimpleTensorTrain +mps = MPS(tt) # TreeTensorNetwork{Int} +``` + +Once you have an MPS, the full TreeTN API is available: + +```julia +println("Number of sites: ", nv(mps)) +println("Bond dimensions: ", linkdims(mps)) + +# Orthogonalize toward site 1 +orthogonalize!(mps, 1) + +# Truncate bond dimensions +truncate!(mps; maxdim=10) +println("Bond dimensions after truncation: ", linkdims(mps)) +``` + +## API summary + +| Function | Description | +|----------|-------------| +| `DiscretizedGrid(ndims, R, lo, hi)` | Create a uniform grid with ``2^R`` points per dimension | +| `quanticscrossinterpolate(grid, f; tolerance, max_bonddim)` | Run quantics TCI | +| `evaluate(ci, [i])` | Evaluate the interpolant at 1-based grid index `i` | +| `integral(ci)` | Integral over the continuous domain | +| `sum(ci)` | Sum of all grid-point values | +| `rank(ci)` | Maximum bond dimension | +| `link_dims(ci)` | Vector of bond dimensions | +| `grididx_to_origcoord(grid, [i])` | Grid index to physical coordinate | +| `origcoord_to_grididx(grid, [x])` | Physical coordinate to grid index | +| `to_tensor_train(ci)` | Convert QTCI to `SimpleTensorTrain` | +| `MPS(tt)` | Convert `SimpleTensorTrain` to `TreeTensorNetwork{Int}` | +| `nv(mps)` | Number of vertices | +| `linkdims(mps)` | Bond dimensions of the MPS | +| `orthogonalize!(mps, v)` | Orthogonalize MPS toward vertex `v` | +| `truncate!(mps; maxdim=d)` | Truncate bond dimensions | From 903e3ba31405bf7398c2523d6da30d46ab1ebee3 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 7 Apr 2026 19:44:14 +0900 Subject: [PATCH 4/5] docs: add 1D Fourier transform tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pipeline: QuanticsTCI → MPS → fourier_operator → set_iospaces! → apply → evaluate. Demonstrates DFT on 2^40-point grid with sum-of-exponentials test function. Shows analytical verification via geometric series and high-frequency access. Migrated from T4APlutoExamples/qft.jl Part A. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/tutorials/fourier1d.md | 283 +++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 2 deletions(-) diff --git a/docs/src/tutorials/fourier1d.md b/docs/src/tutorials/fourier1d.md index 4cccd86..a26b851 100644 --- a/docs/src/tutorials/fourier1d.md +++ b/docs/src/tutorials/fourier1d.md @@ -1,4 +1,283 @@ # 1D Fourier Transform -!!! note "Work in progress" - This tutorial is under construction. See [README](https://github.com/tensor4all/Tensor4all.jl) for usage examples. +This tutorial demonstrates how to compute the **discrete Fourier transform +(DFT)** of a function on an exponentially large quantics grid using +Tensor4all.jl. The key idea is that the DFT matrix itself admits a +low-rank tensor train (QTT) representation, so the transform can be carried +out entirely within the QTT framework -- without ever constructing the full +``2^R \times 2^R`` DFT matrix. + +## Why QTT-based Fourier transforms? + +A standard DFT on ``M = 2^R`` points costs ``O(M \log M)`` via FFT. When +``R = 40``, we have ``M \approx 10^{12}`` -- a trillion points -- and even +the FFT becomes infeasible. The QTT approach bypasses this: if the input +function has a compact QTT representation (small bond dimension), and the DFT +operator also has small bond dimension (it does), then the Fourier +coefficients can be computed by a tensor-train contraction whose cost scales +only with the bond dimensions, not with ``M``. + +This is particularly valuable when you need Fourier coefficients at +*specific* frequencies (small ``k`` or large ``k``) rather than the entire +spectrum, since individual coefficients can be evaluated in ``O(R)`` time +once the output QTT is available. + +## Setup + +```julia +using Tensor4all.QuanticsGrids # Grid types and coordinate conversions +using Tensor4all.QuanticsTCI # quanticscrossinterpolate +using Tensor4all.SimpleTT # to_tensor_train +using Tensor4all.TreeTN # MPS, linkdims, ... +using Tensor4all.QuanticsTransform # fourier_operator, set_iospaces!, apply +using Tensor4all.TreeTCI: evaluate as ttn_evaluate # evaluate TTN at quantics index +``` + +## Problem: Fourier transform of a sum of exponentials + +We consider a function on ``[0, 1]`` that mimics a bosonic correlator: + +```math +f(x) = \sum_p \frac{c_p}{1 - e^{-\varepsilon_p}}\, e^{-\varepsilon_p\, x} +``` + +with parameters ``c_1 = 1.5``, ``\varepsilon_1 = 3.0``, ``c_2 = 0.7``, +``\varepsilon_2 = 15.0``. This function varies rapidly near ``x = 0`` (due +to the ``\varepsilon_2 = 15`` term) and has a slow exponential tail, +creating multi-scale structure ideally suited for quantics representation. + +```julia +c = [1.5, 0.7] +eps = [3.0, 15.0] + +f(x) = Base.sum(c[p] * exp(-eps[p] * x) / (1 - exp(-eps[p])) for p in 1:2) +``` + +!!! note + We write `Base.sum` instead of `sum` because `QuanticsTCI` also exports + a `sum` function. Using the qualified name avoids ambiguity. + +## Step 1: Quantics cross-interpolation + +Create a quantics grid with ``R = 40`` bits (``2^{40} \approx 10^{12}`` +points) and build the QTT approximation: + +```julia +R = 40 +grid = DiscretizedGrid(1, R, [0.0], [1.0]) + +ci, ranks, errors = quanticscrossinterpolate(grid, f; tolerance=1e-10) +``` + +The maximum bond dimension will be small (typically around 10--15), +reflecting the low complexity of exponentials in the quantics representation. + +## Step 2: Convert to MPS + +Convert the QTCI result to a `TreeTensorNetwork` (MPS) for use with the +Fourier operator: + +```julia +tt = to_tensor_train(ci) # SimpleTensorTrain +fmps = MPS(tt) # TreeTensorNetwork{Int} +``` + +## Step 3: Construct and apply the Fourier operator + +The `fourier_operator` function creates the QTT representation of the DFT +matrix with the convention + +```math +\hat{f}_k = \frac{1}{\sqrt{M}} \sum_{m=0}^{M-1} f_m\, e^{-2\pi i\, k m / M}, +\qquad M = 2^R. +``` + +Before applying the operator, its site indices must be bound to those of the +input MPS via `set_iospaces!`: + +```julia +ft_op = fourier_operator(R; forward=true) +set_iospaces!(ft_op, fmps) +hfmps = apply(ft_op, fmps; method=:naive) +``` + +!!! tip + The `method=:naive` contraction is the simplest and works well when + bond dimensions are moderate. For larger problems, `:zipup` or `:fit` + can reduce the bond dimension of the result at the cost of a controlled + approximation error. + +## Step 4: Evaluate Fourier coefficients + +To evaluate ``\hat{f}_k`` at a specific frequency index ``k``, convert +``k`` to its binary (quantics) representation and call `ttn_evaluate`: + +```julia +k = 5 +bits = digits(k, base=2, pad=R) # LSB-first bit vector, length R +val = ttn_evaluate(hfmps, bits) +``` + +The `digits` function returns the bits in LSB-first order, which is the +correct convention for `ttn_evaluate` -- no reversal is needed. + +!!! note + The result `hfmps` from `apply` is a `TreeTensorNetwork` that may not + satisfy `is_chain(hfmps)`. Always use `ttn_evaluate` to read out + values, rather than assuming a chain topology. + +## Step 5: Compare with the analytical DFT + +Each exponential term ``g_p(x) = c_p / (1 - e^{-\varepsilon_p})\, e^{-\varepsilon_p\, x}`` +sampled at ``x = m/M`` gives a geometric series in the DFT. The exact +discrete Fourier coefficient is: + +```math +\hat{f}_k = \frac{1}{\sqrt{M}} \sum_p + \frac{c_p}{1 - e^{-\varepsilon_p}} + \cdot \frac{1 - e^{-(\varepsilon_p + 2\pi i k)}}{1 - e^{-(\varepsilon_p + 2\pi i k)/M}} +``` + +This follows from summing the geometric series +``\sum_{m=0}^{M-1} r^m = (1 - r^M)/(1 - r)`` with +``r = e^{-(\varepsilon_p + 2\pi i k)/M}``. + +In Julia: + +```julia +M = 2^R + +function analytical_dft(k) + result = zero(ComplexF64) + for p in 1:2 + z = eps[p] + 2π * im * k + geom = (1 - exp(-z)) / (1 - exp(-z / M)) + result += c[p] / (1 - exp(-eps[p])) * geom + end + return result / sqrt(M) +end +``` + +Check the accuracy at a few frequencies: + +```julia +for k in [0, 1, 5, 100] + bits = digits(k, base=2, pad=R) + qft_val = ttn_evaluate(hfmps, bits) + exact_val = analytical_dft(k) + rel_err = abs(qft_val - exact_val) / abs(exact_val) + println("k = $k: QFT = $qft_val, exact = $exact_val, rel_err = $rel_err") +end +``` + +## High-frequency accuracy + +A major advantage of the QTT Fourier transform is access to high-frequency +coefficients at no additional cost. Evaluating ``\hat{f}_{10000}`` is just +as cheap as evaluating ``\hat{f}_1``: + +```julia +for k in [10, 100, 1000, 10000] + bits = digits(k, base=2, pad=R) + qft_val = ttn_evaluate(hfmps, bits) + exact_val = analytical_dft(k) + rel_err = abs(qft_val - exact_val) / abs(exact_val) + println("k = $k: rel_err = $rel_err") +end +``` + +With tolerance ``10^{-10}`` in the QTCI step, relative errors are typically +``10^{-10}`` or better across all frequencies. + +## Complete example + +Putting it all together: + +```julia +using Tensor4all.QuanticsGrids +using Tensor4all.QuanticsTCI +using Tensor4all.SimpleTT +using Tensor4all.TreeTN +using Tensor4all.QuanticsTransform +using Tensor4all.TreeTCI: evaluate as ttn_evaluate + +# Parameters +c = [1.5, 0.7] +eps = [3.0, 15.0] +R = 40 +M = 2^R + +# Target function +f(x) = Base.sum(c[p] * exp(-eps[p] * x) / (1 - exp(-eps[p])) for p in 1:2) + +# 1. QTCI on a trillion-point grid +grid = DiscretizedGrid(1, R, [0.0], [1.0]) +ci, ranks, errors = quanticscrossinterpolate(grid, f; tolerance=1e-10) + +# 2. Convert to MPS +tt = to_tensor_train(ci) +fmps = MPS(tt) + +# 3. Apply Fourier operator +ft_op = fourier_operator(R; forward=true) +set_iospaces!(ft_op, fmps) +hfmps = apply(ft_op, fmps; method=:naive) + +# 4. Analytical reference (exact geometric series) +function analytical_dft(k) + result = zero(ComplexF64) + for p in 1:2 + z = eps[p] + 2π * im * k + geom = (1 - exp(-z)) / (1 - exp(-z / M)) + result += c[p] / (1 - exp(-eps[p])) * geom + end + return result / sqrt(M) +end + +# 5. Compare at low and high k +for k in [0, 1, 5, 100, 10000] + bits = digits(k, base=2, pad=R) + qft_val = ttn_evaluate(hfmps, bits) + exact_val = analytical_dft(k) + rel_err = abs(qft_val - exact_val) / abs(exact_val) + println("k = $k: rel_err = $(round(rel_err, sigdigits=2))") +end +``` + +## The pipeline at a glance + +The overall workflow is: + +``` +f(x) → QTCI → MPS → Fourier operator × MPS → evaluate at k +``` + +Each step is efficient: the QTCI scales with bond dimension, not grid size; +the Fourier operator has ``O(1)`` bond dimension per site; the contraction +is a standard tensor-train operation; and pointwise evaluation costs +``O(R)``. + +## API summary + +| Function | Description | +|----------|-------------| +| `quanticscrossinterpolate(grid, f; tolerance)` | Build QTT via cross-interpolation | +| `to_tensor_train(ci)` | Convert QTCI result to `SimpleTensorTrain` | +| `MPS(tt)` | Convert `SimpleTensorTrain` to `TreeTensorNetwork{Int}` | +| `fourier_operator(R; forward=true)` | Create QTT Fourier operator (`LinearOperator`) | +| `set_iospaces!(op, mps)` | Bind operator site indices to the input MPS | +| `apply(op, mps; method=:naive)` | Apply operator to MPS, return `TreeTensorNetwork` | +| `ttn_evaluate(ttn, bits)` | Evaluate TTN at a quantics bit vector | +| `digits(k, base=2, pad=R)` | Convert integer `k` to LSB-first bit vector | + +### Normalization convention + +The forward Fourier operator implements: + +```math +\hat{f}_k = \frac{1}{\sqrt{M}} \sum_{m=0}^{M-1} f_m\, e^{-2\pi i\, k m / M} +``` + +This is the **symmetric** convention with ``1/\sqrt{M}`` normalization and a +**negative** sign in the exponent. When comparing with analytical results, +ensure your reference formula uses the same convention. From bb498b43e4f546199574bb26ab0a43813b40d910 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 7 Apr 2026 19:53:33 +0900 Subject: [PATCH 5/5] feat: add swap_site_indices! and rearrange_siteinds - C_API.jl: add t4a_treetn_swap_site_indices binding - TreeTN.jl: add swap_site_indices!(ttn, target) and rearrange_siteinds - Tensor4all.jl: export new functions Note: adjacent swaps work; non-adjacent multi-site swaps may fail due to a convergence limitation in the Rust swap algorithm (to be fixed upstream). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/C_API.jl | 25 ++++++++++++++ src/Tensor4all.jl | 1 + src/TreeTN.jl | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/src/C_API.jl b/src/C_API.jl index a34af3f..eb28e5c 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -2499,4 +2499,29 @@ function t4a_linop_set_output_space(op::Ptr{Cvoid}, state::Ptr{Cvoid}) ) end +# ============================================================================ +# TreeTN: swap_site_indices +# ============================================================================ + +""" + t4a_treetn_swap_site_indices(ttn, index_ids, target_vertices, n_pairs, max_rank, rtol) -> Cint + +Swap site indices on a TreeTN to a target assignment. +""" +function t4a_treetn_swap_site_indices( + ttn::Ptr{Cvoid}, + index_ids::Ptr{UInt64}, + target_vertices::Ptr{Csize_t}, + n_pairs::Csize_t, + max_rank::Csize_t, + rtol::Cdouble, +) + return ccall( + _sym(:t4a_treetn_swap_site_indices), + Cint, + (Ptr{Cvoid}, Ptr{UInt64}, Ptr{Csize_t}, Csize_t, Csize_t, Cdouble), + ttn, index_ids, target_vertices, n_pairs, max_rank, rtol + ) +end + end # module C_API diff --git a/src/Tensor4all.jl b/src/Tensor4all.jl index 4666bad..2f221ee 100644 --- a/src/Tensor4all.jl +++ b/src/Tensor4all.jl @@ -1069,6 +1069,7 @@ include("SimpleTT.jl") include("TreeTN.jl") using .TreeTN: MPS, MPO, TensorTrain, random_mps, random_tt, is_chain, is_mps_like, is_mpo_like export MPS, MPO, TensorTrain, random_mps, random_tt, is_chain, is_mps_like, is_mpo_like +export swap_site_indices!, rearrange_siteinds # ============================================================================ # QuanticsGrids Submodule diff --git a/src/TreeTN.jl b/src/TreeTN.jl index 8286f9e..ac8c527 100644 --- a/src/TreeTN.jl +++ b/src/TreeTN.jl @@ -1096,4 +1096,88 @@ end export save_mps, load_mps +# ============================================================================ +# swap_site_indices! +# ============================================================================ + +""" + swap_site_indices!(ttn::TreeTensorNetwork, target::Dict; + maxdim::Int=0, rtol::Float64=0.0) + +Swap site indices to a target vertex assignment. The tensor network values +are preserved via SVD factorization along each edge. + +`target` maps `id(index)` (as `UInt64`) to target vertex number (1-based). + +# Arguments +- `target`: mapping from index ID to target vertex +- `maxdim`: maximum bond dimension after SVD (0 = no limit) +- `rtol`: relative SVD truncation tolerance (0.0 = no truncation) +""" +function swap_site_indices!(ttn::TreeTensorNetwork, target::Dict; + maxdim::Int=0, rtol::Float64=0.0) + n = length(target) + n > 0 || return ttn + + index_ids = Vector{UInt64}(undef, n) + target_vertices = Vector{Csize_t}(undef, n) + for (i, (idx_id, vertex)) in enumerate(target) + index_ids[i] = UInt64(idx_id) + target_vertices[i] = Csize_t(vertex - 1) # 0-based for C API + end + + GC.@preserve index_ids target_vertices begin + status = C_API.t4a_treetn_swap_site_indices( + ttn.handle, pointer(index_ids), pointer(target_vertices), + Csize_t(n), Csize_t(maxdim), Float64(rtol)) + C_API.check_status(status) + end + return ttn +end + +export swap_site_indices! + +""" + rearrange_siteinds(ttn::TreeTensorNetwork, target_layout::Vector{Vector{Index}}; + maxdim::Int=0, rtol::Float64=0.0) -> TreeTensorNetwork + +Rearrange site indices so that vertex `v` holds the indices in `target_layout[v]`. +Returns a new TreeTensorNetwork (the input is not modified). + +Uses `swap_site_indices!` internally. +""" +function rearrange_siteinds(ttn::TreeTensorNetwork, target_layout::Vector{Vector{Index}}; + maxdim::Int=0, rtol::Float64=0.0) + n = nv(ttn) + length(target_layout) == n || throw(DimensionMismatch( + "target_layout has $(length(target_layout)) entries but TTN has $n vertices")) + + # Build complete target assignment for ALL site indices + # (swap_site_indices requires every site index to have a target) + target = Dict{UInt64, Int}() + + # First, include explicitly specified targets + for (v, inds) in enumerate(target_layout) + for idx in inds + target[UInt64(id(idx))] = v + end + end + + # Then, include any site indices not mentioned (keep them at current vertex) + for v in vertices(ttn) + for idx in siteinds(ttn, v) + idx_id = UInt64(id(idx)) + if !haskey(target, idx_id) + target[idx_id] = v + end + end + end + + result = copy(ttn) + swap_site_indices!(result, target; maxdim=maxdim, rtol=rtol) + return result +end + +export rearrange_siteinds + end # module TreeTN