Tree Tensor Network Structures
pyQuTree supports different tree tensor network topologies, each with distinct properties and performance characteristics. This guide explains when and how to use each structure.
Overview
Tree tensor networks decompose high-dimensional data into a hierarchical structure. The choice of topology affects:
Computational complexity
Memory requirements
Convergence speed
Scalability with dimension
Available Structures
pyQuTree provides two main network structures:
Tensor Train (TT): Linear chain topology
Balanced Tree (BT): Hierarchical binary tree topology
Tensor Train Networks
The tensor train is a linear chain where each dimension is connected sequentially.
Creating a Tensor Train
from qutree import tensor_train_graph, plot_tt_diagram
import numpy as np
f = 5 # number of dimensions
r = 4 # bond dimension
N = 21 # grid points per dimension
# Create tensor train graph
G = tensor_train_graph(f, r, N)
# Visualize the structure
fig = plot_tt_diagram(G)
Structure
In a tensor train with f dimensions:
Each dimension is represented by a node in a linear chain
Adjacent nodes are connected with bond dimension r
Total number of parameters: O(f × N × r²)
Visual representation for f=3:
[x1]---r---[x2]---r---[x3]
| | |
N N N
Where:
* [xi] represents dimension i
* r is the bond dimension connecting adjacent nodes
* N is the number of grid points per dimension
Computational Complexity
Storage: O(f × N × r²)
Optimization sweep: O(f × N × r² × eval)
Scaling with f: Linear
Example Usage
from qutree import *
import numpy as np
def objective_function(x):
# Minimize sum of squared deviations
target = np.arange(len(x))
return np.sum((x - target)**2)
objective = Objective(objective_function)
# Setup
f, r, N, nsweeps = 10, 4, 21, 3
# Create tensor train
G = tensor_train_graph(f, r, N)
# Define grid
primitive_grid = [np.linspace(0., float(f-1), num=N)] * f
# Optimize
G_opt = ttnopt(G, objective, nsweeps, primitive_grid)
When to Use Tensor Trains
Advantages:
Simple structure, easy to understand
Efficient for low-to-medium dimensions (f < 20)
Well-studied with strong theoretical guarantees
Good for problems with sequential structure
Disadvantages:
Linear scaling in f (can be slow for f > 50)
Information must pass through the entire chain
Asymmetric: end dimensions are treated differently than middle ones
Balanced Tree Networks
The balanced tree organizes dimensions in a hierarchical binary tree structure.
Creating a Balanced Tree
from qutree import balanced_tree, plot_tree
import numpy as np
f = 8 # number of dimensions (works best with powers of 2)
r = 4 # bond dimension
N = 21 # grid points per dimension
# Create balanced tree
G = balanced_tree(f, r, N)
# Visualize the structure
fig = plot_tree(G)
Structure
In a balanced tree with f dimensions:
Leaf nodes represent physical dimensions
Internal nodes combine information hierarchically
Tree depth: O(log₂(f))
More symmetric than tensor train
Visual representation for f=4:
[root]
/ \\
[n1] [n2]
/ \\ / \\
[x1][x2] [x3][x4]
| | | |
N N N N
Computational Complexity
Storage: O(f × N × r + r³ × log₂(f))
Optimization sweep: O(f × N × r + r³ × log₂(f) × eval)
Scaling with f: Logarithmic
This is significantly better than tensor train for large f!
Example Usage
from qutree import *
import numpy as np
def high_dim_function(x):
# Example: Rosenbrock-like function
result = 0.0
for i in range(len(x)-1):
result += 100*(x[i+1] - x[i]**2)**2 + (1 - x[i])**2
return result
objective = Objective(high_dim_function)
# Setup for high-dimensional problem
f, r, N, nsweeps = 32, 6, 25, 4
# Use balanced tree for better scaling
G = balanced_tree(f, r, N)
# Define grid
primitive_grid = [np.linspace(-2., 2., num=N)] * f
# Optional: provide starting points near expected minimum
start_grid = np.ones((f, r)) # Start near x=1
# Optimize
G_opt = ttnopt(G, objective, nsweeps, primitive_grid, start_grid)
When to Use Balanced Trees
Advantages:
Logarithmic scaling in f (excellent for f > 20)
More symmetric treatment of all dimensions
Better for very high-dimensional problems
Faster convergence for some problem types
Disadvantages:
More complex structure
Slightly more overhead for small f
Works best when f is a power of 2
Comparison
Complexity Comparison
For same rank r and dimension N:
Structure |
Storage |
Sweep Cost |
|---|---|---|
Tensor Train |
f × N × r² |
f × N × r² × eval |
Balanced Tree |
f × N × r + r³×log₂(f) |
(f×N×r + r³×log₂(f))×eval |
For large f, balanced tree is significantly more efficient!
Example: f=64, r=4, N=20
Tensor Train: ~327,000 parameters
Balanced Tree: ~5,500 parameters (59× smaller!)
Performance Guidelines
Choose Tensor Train when:
f < 20 (low to medium dimensions)
Problem has sequential/chain structure
Simplicity is important
You need theoretical guarantees
Choose Balanced Tree when:
f > 20 (high dimensions)
f is a power of 2 (optimal structure)
Memory is constrained
Problem has hierarchical structure
Example Benchmark
Minimizing f-dimensional quadratic function (x - target)²:
import time
from qutree import *
import numpy as np
def benchmark(f, structure='tt'):
def V(x):
target = np.arange(len(x))
return np.sum((x - target)**2)
objective = Objective(V)
r, N, nsweeps = 4, 21, 3
if structure == 'tt':
G = tensor_train_graph(f, r, N)
else:
G = balanced_tree(f, r, N)
primitive_grid = [np.linspace(0., float(f), num=N)] * f
start = time.time()
G_opt = ttnopt(G, objective, nsweeps, primitive_grid)
elapsed = time.time() - start
return elapsed, objective.ncalls
# Compare for different dimensions
for f in [8, 16, 32, 64]:
tt_time, tt_calls = benchmark(f, 'tt')
bt_time, bt_calls = benchmark(f, 'bt')
print(f"f={f:2d}: TT={tt_time:.2f}s ({tt_calls} calls), "
f"BT={bt_time:.2f}s ({bt_calls} calls)")
Advanced: Custom Tree Structures
For specialized applications, you can build custom tree structures using networkx:
import networkx as nx
from qutree import TensorNetwork
# Create custom graph
G = nx.DiGraph()
# Add nodes with attributes
# Leaf nodes: have 'primitive_grid' attribute
# Internal nodes: have 'rank' attribute
# Example: star topology (one central node connected to all leaves)
G.add_node(0, rank=4) # central node
for i in range(1, f+1):
G.add_node(i, primitive_grid=np.linspace(0., 4., 21))
G.add_edge(0, i) # connect to center
# Use in optimization
# (Note: this is advanced usage, standard structures are recommended)
Best Practices
Start Simple: Begin with tensor train for initial experiments
Scale Up: Switch to balanced tree when f > 20
Bond Dimension: Start with r=4, increase if needed
Grid Resolution: Start with N=21, adjust based on precision needs
Monitor Convergence: Track objective value across sweeps
Use Start Points: Warm-start with domain knowledge when possible
Example Workflow
from qutree import *
import numpy as np
# 1. Define your problem
def my_objective(x):
return np.sum(x**2) # simple example
objective = Objective(my_objective)
# 2. Choose structure based on dimensionality
f = 50 # high-dimensional -> use balanced tree
# 3. Start with conservative parameters
r, N, nsweeps = 4, 21, 3
# 4. Create appropriate structure
G = balanced_tree(f, r, N) if f > 20 else tensor_train_graph(f, r, N)
# 5. Define search space
primitive_grid = [np.linspace(-5., 5., num=N)] * f
# 6. Run optimization
G_opt = ttnopt(G, objective, nsweeps, primitive_grid)
# 7. Check convergence
df = objective.logger.df
print(f"Best value: {df['f'].min()}")
print(f"Function calls: {objective.ncalls}")
# 8. If not converged, increase nsweeps or r
See Also
Tree Tensor Network Optimization (TTNOpt) - Detailed optimization guide
Quick Start - Getting started tutorial
API Reference - API reference