TensorCircuit Documentation

repository·master·Indexed 18 days ago

https://github.com/tencent-quantum-lab/tensorcircuit

A high-performance quantum software framework for efficient quantum-classical hybrid simulations and variational algorithms. It supports large-scale simulations, automatic differentiation, and hardware acceleration by leveraging ML backends such as Jax, TensorFlow, and PyTorch, as well as tensor network engines. Key features include the Circuit and DMCircuit classes for state and noise simulation, sparse Hamiltonian generation, and integration with remote quantum devices via a cloud service token.

Tokens
50.2K
Snippets
168
Records
195
Agent score
62%

What's inside TensorCircuit

  1. Overview of TensorCircuit Modules

    master

    TensorCircuit is organized into several functional modules depending on your use case:

    Core Simulation

    • tensorcircuit.circuit: The primary Circuit object for construction, simulation (with or without noise), and visualization.
    • tensorcircuit.gates: Definitions for fixed and parameterized quantum gates.
    • tensorcircuit.abstractcircuit & tensorcircuit.basecircuit: Hierarchical abstractions for circuit classes.
    • tensorcircuit.cons: Handles runtime ML backend, dtype, and contractor setups via global methods, decorators, or context managers.

    Noise and Density Matrix Simulation

    • tensorcircuit.channels: Quantum noise channel definitions.
    • tensorcircuit.densitymatrix: Efficient implementation of DMCircuit for full density matrix simulation.
    • tensorcircuit.noisemodel: Global noise configuration and noisy method APIs.

    Machine Learning Interfaces

    • tensorcircuit.interfaces: Optimizers for PyTorch, TensorFlow, NumPy, and SciPy.
    • tensorcircuit.keras: TensorFlow Keras layers and wrappers.
    • tensorcircuit.torchnn: PyTorch nn.Module implementations.

    Tensor Network and MPS

    • tensorcircuit.quantum: Matrix Product States (MPS) and Matrix Product Operators (MPO) definitions.
    • tensorcircuit.mps_base: JIT/AD compatible MPS classes.
    • tensorcircuit.mpscircuit: MPSCircuit class using MPS TEBD simulation.

    Utilities and Extras

    • tensorcircuit.vis: Circuit visualization.
    • tensorcircuit.results: Result processing and error mitigation.
    • tensorcircuit.cloud: Quantum Cloud SDK for real hardware access.
    • tensorcircuit.compiler: Circuit transformation and compilation chains.
    • tensorcircuit.templates: Shortcuts for expectation values or circuit building patterns.
  2. Introduction to TensorCircuit

    master

    TensorCircuit is a high-performance quantum computing software framework in Python designed for speed, flexibility, and elegance. It uses an advanced tensor network simulator engine and is compatible with industry-standard machine learning frameworks, enabling features like automatic differentiation, just-in-time (JIT) compilation, vectorized parallelism, and GPU acceleration.

    Key capabilities include:

    • Hybrid Solutions: Ready for quantum hardware access via CPU, GPU, and QPU (local or cloud) solutions.
    • ML Integration: Implemented with TensorFlow, JAX, and PyTorch.
    • Unified Programming: Provides unified backends (Jax, TensorFlow, PyTorch, Numpy, Cupy), devices (CPU, GPU, TPU), and providers (various QPU vendors).
  3. Use MPS (Matrix Product States) and MPO (Matrix Product Operators)

    master

    TensorCircuit integrates with TensorNetwork's MPS/MPO concepts via tc.QuVector (for states/wavefunctions) and tc.QuOperator (for operators).

    • MPS as Input: Create a tc.QuVector and pass it to tc.Circuit(n, mps_inputs=w).
    • MPS as Output: Extract the uncontracted state from a circuit using c.quvector().
    • MPO as Gates: Apply an MPO directly to a circuit using c.mpo(qubit_indices, mpo=mpo_object).
    • MPO for Expectation: Measure an operator in MPO format on a circuit using tc.templates.measurements.mpo_expectation(circuit, mpo).
    import tensorcircuit as tc
    import numpy as np
    
    # 1. MPS as input state
    n = 3
    nodes = [tc.gates.Gate(np.array([0.0, 1.0])) for _ in range(n)]
    mps = tc.quantum.QuVector([nd[0] for nd in nodes])
    c = tc.Circuit(n, mps_inputs=mps)
    
    # 2. MPO as a gate
    x0, x1 = tc.gates.x(), tc.gates.x()
    mpo = tc.quantum.QuOperator([x0[0], x1[0]], [x0[1], x1[1]])
    c_mpo = tc.Circuit(2)
    c_mpo.mpo(0, 1, mpo=mpo)
    
    # 3. MPO for expectation value
    z0, z1 = tc.gates.z(), tc.gates.z()
    z_mpo = tc.quantum.QuOperator([z0[0], z1[0]], [z0[1], z1[1]])
    c_exp = tc.Circuit(2)
    c_exp.X(0)
    val = tc.templates.measurements.mpo_expectation(c_exp, z_mpo)
  4. Quantum Cloud SDK API Layers

    master

    The tensorcircuit.cloud module provides a layered API for interacting with Quantum Processing Units (QPUs), ranging from low-level vendor implementations to high-level application interfaces:

    1. Vendor Implementation: Specific functional APIs (e.g., tensorcircuit.cloud.tencent).
    2. Provider Agnostic API: Lower-level functional API for task and device management in tensorcircuit.cloud.apis.
    3. Object-Oriented Abstraction: Abstractions for Provider, Device, and Task in tensorcircuit.cloud.abstraction.
    4. Unified Batch Submission: Standardized interface via tensorcircuit.cloud.wrapper.batch_submit_template.
    5. Numerical/Experimental Interface: All-in-one interface via tensorcircuit.cloud.wrapper.batch_expectation_ps.
    6. Application Level: High-level algorithms built on batch_expectation_ps or batch_submit_func for easy reuse across different vendors.
  5. When to use GPU for quantum simulation

    master

    As a general rule of thumb, GPU simulation is faster than CPU simulation when:

    • The qubit count is larger than 16.
    • The circuit simulation uses a large batch dimension (greater than 16).

    For very small circuits or very small batch dimensions, CPU simulation may perform better. Always perform detailed benchmarks on your specific hardware and task.

  6. When to use JIT (Just-In-Time) compilation

    master

    Wrapping a function with jit can greatly accelerate evaluation for functions with a "tensor in and tensor out" pattern.

    Important Considerations:

    • Staging Time: The first evaluation takes longer due to the staging process. Only use jit for functions that are evaluated frequently.
    • Risks of Misuse: Improper use can lead to slow performance (due to constant recompilation), errors, or incorrect results.
    • Common Pitfalls:
      • Inputting non-tensor types.
      • Output shapes that depend on input values rather than input shapes.
      • Mixing NumPy operations with ML framework operations.
      • Subtle issues with random number generation and jit.
  7. Understand AD behavior differences across backends

    master

    Automatic Differentiation (AD) for complex-valued functions is not identical across all backends. TensorFlow and JAX manage differentiation rules for complex numbers differently (specifically regarding complex conjugates).

    When switching backends (e.g., from tensorflow to jax), the results of grad or jacrev for complex-valued functions may change. Users should be aware that TensorCircuit's AD behavior is determined by the underlying framework's nature. If you require backend-agnostic results, you may need to design your functions to be real-valued or explicitly handle the complex conjugation logic.

    # Note: Results for complex-valued functions may differ between backends
    bks = ["tensorflow", "jax"]
    for bk in bks:
        with tc.runtime_backend(bk) as K:
            def wfn(params):
                # ... circuit logic ...
                return K.real(c.expectation_ps(z=[0]))
            print(K.grad(wfn)(K.ones([n], dtype="complex64")))
  8. How QuOperator and QuVector work with TensorNetwork

    master

    TensorCircuit uses tc.quantum.QuOperator, tc.quantum.QuVector, and tc.quantum.QuAdjointVector to wrap TensorNetwork nodes. These objects behave like matrices and vectors while maintaining an efficient underlying tensor network structure.

    Key Concepts

    • QuOperator: Represents any tensor network with two sets of dangling edges of the same dimension. It can express MPOs but is more general. Defined by providing out_edges (row indices) and in_edges (column indices).
    • QuVector: Represents MPS-like structures where all dangling edges are treated as the vector dimension.
    • Operations: Supports standard linear algebra like matmul (@), adjoint (.adjoint()), scalar multiplication (*), tensor product (|), and .partial_trace(subsystems_to_trace_out).
    • Evaluation:
      • .eval(): Keeps the shape information of the tensor network.
      • .eval_matrix(): Returns the matrix representation with rank 2.

    Usage Example

    import tensornetwork as tn
    import numpy as np
    import tensorcircuit as tc
    
    # Setup nodes
    n1 = tn.Node(np.ones([2, 2, 2]))
    n2 = tn.Node(np.ones([2, 2, 2]))
    n3 = tn.Node(np.ones([2, 2]))
    
    # Connect nodes using ^ (tn.connect)
    n1[2]^n2[2]
    n2[1]^n3[0]
    
    # Create a QuOperator
    matrix = tc.quantum.QuOperator(out_edges=[n1[0], n2[0]], in_edges=[n1[1], n3[1]])
    
    # Create a QuVector
    n4 = tn.Node(np.ones([2]))
    n5 = tn.Node(np.ones([2]))
    vector = tc.quantum.QuVector([n4[0], n5[0]])
    
    # Perform matrix-vector multiplication
    nvector = matrix @ vector 
    
    assert type(nvector) == tc.quantum.QuVector
    nvector.eval_matrix() 
    # Output: array([[16.], [16.], [16.], [16.]])
    import tensornetwork as tn
    import numpy as np
    import tensorcircuit as tc
    
    n1 = tn.Node(np.ones([2, 2, 2]))
    n2 = tn.Node(np.ones([2, 2, 2]))
    n3 = tn.Node(np.ones([2, 2]))
    n1[2]^n2[2]
    n2[1]^n3[0]
    
    matrix = tc.quantum.QuOperator(out_edges=[n1[0], n2[0]], in_edges=[n1[1], n3[1]])
    
    n4 = tn.Node(np.ones([2]))
    n5 = tn.Node(np.ones([2]))
    
    vector = tc.quantum.QuVector([n4[0], n5[0]])
    
    nvector = matrix @ vector 
    
    assert type(nvector) == tc.quantum.QuVector
    nvector.eval_matrix() 
    # array([[16.], [16.], [16.], [16.]])
  9. Handle random numbers in jitted functions across backends

    master

    When using @K.jit, handling random numbers requires care to ensure backend agnosticity and correctness, especially with the JAX backend which uses explicit PRNG keys.

    To write a unified, jittable, and backend-agnostic function that handles random numbers correctly, you should use K.get_random_state() to obtain a key and pass it into the jitted function, then re-set the state inside the function using K.set_random_state(key).

    Warning: If you are using vmap in conjunction with jit and random numbers, it is strongly recommended to use the JAX backend, as TensorFlow currently lacks support for vmap over random keys.

    import tensorcircuit as tc
    
    K = tc.set_backend("tensorflow") # or "jax"
    
    # The recommended pattern for backend-agnostic jitted randoms:
    key = K.get_random_state(42)
    
    @K.jit
    def r(key):
        K.set_random_state(key)
        return K.implicit_randn()
    
    key1, key2 = K.random_split(key)
    print(r(key1), r(key2)) # Correctly produces different values
  10. Programming Paradigm: Variational Quantum Algorithms

    master

    The primary use case for TensorCircuit is evaluating circuit outputs and quantum gradients, typically for Variational Quantum Algorithms (VQAs).

    TensorCircuit follows a functional programming design pattern similar to JAX. It is highly recommended to use the backend's JIT (Just-In-Time) compilation to boost simulation speed by 2-3 orders of magnitude.

    Backend Agnostic Pattern: Use the backend object K (returned by tc.set_backend) to handle JIT, gradients, and random states. This allows your code to remain compatible even if you switch from JAX to TensorFlow or PyTorch.

    import tensorcircuit as tc
    
    K = tc.set_backend("jax") # or "tensorflow", "torch"
    
    def loss(params, n):
        c = tc.Circuit(n)
        for i in range(n):
            c.rx(i, theta=params[0, i])
        # ... compute expectation ...
        return K.real(loss_val)
    
    # Use K for JIT and Gradients
    vgf = K.jit(K.value_and_grad(loss), static_argnums=1)
    import tensorcircuit as tc
    
    K = tc.set_backend("tensorflow")
    n = 1
    
    def loss(params, n):
        c = tc.Circuit(n)
        for i in range(n):
            c.rx(i, theta=params[0, i])
            c.rz(i, theta=params[1, i])
        loss_val = 0.0
        for i in range(n):
            loss_val += c.expectation([tc.gates.z(), [i]])
        return K.real(loss_val)
    
    # Compute loss and gradient using backend-specific JIT
    vgf = K.jit(K.value_and_grad(loss), static_argnums=1)
    params = K.implicit_randn([2, n])
    print(vgf(params, n))
  11. Customize the contraction strategy

    master

    For circuits with qubit counts > 16 and depth > 8, customized contraction may outperform the default greedy strategy.

    Trade-off: A customized contractor takes more time to find a contraction path but results in faster and more memory-efficient real contraction via matmul. Because jit is typically used, the pathfinding cost is only incurred during the first run, making customized contractors highly beneficial.

    Recommendation: Use the cotengra library to set up a customized contractor for optimal performance tuning.