State evolution - pyqtorch

Skip to content

# State evolution

This tutorial shows how to apply the `hamiltonian_evolution` operator to evolve the quantum state using an approximated but efficient matrix exponentiation.

In [1]:
Copied!

    from pyqtorch.matrices import single_Z, ZZ
    from pyqtorch.ansatz import AlternateLayerAnsatz, OneLayerXRotation, OneLayerZRotation, OneLayerEntanglingAnsatz
    from pyqtorch.core.circuit import QuantumCircuit

from pyqtorch.matrices import single_Z, ZZ from pyqtorch.ansatz import AlternateLayerAnsatz, OneLayerXRotation, OneLayerZRotation, OneLayerEntanglingAnsatz from pyqtorch.core.circuit import QuantumCircuit

    /home/mdagrada/.cache/pypoetry/virtualenvs/qucint-YinaOvwL-py3.8/lib/python3.8/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
      from .autonotebook import tqdm as notebook_tqdm

In [2]:
Copied!

    import torch as th
    import numpy as np
    import copy

import torch as th import numpy as np import copy

We start initialising the `QuantumCircuit` instance in order to observe the typical shape of an input/output state in the PyQ format

In [3]:
Copied!

    N = 4
    qc = QuantumCircuit(N)
    psi = qc.uniform_state(1)
    psi_0 = copy.deepcopy(psi)
    psi_0.shape

N = 4 qc = QuantumCircuit(N) psi = qc.uniform_state(1) psi_0 = copy.deepcopy(psi) psi_0.shape
Out[3]:

    torch.Size([2, 2, 2, 2, 1])

We perform a deepcopy of `psi` as some operations below (e.g. `hamiltonian evolution`) will overwrite it.

In [4]:
Copied!

    def overlap(state1, state2):
        N = len(state1.shape)-1
        state1_T = th.transpose(state1, N, 0)
        overlap = th.tensordot(state1.T.conj(), state2, dims=N)
        return float(th.abs(overlap**2).flatten())

def overlap(state1, state2): N = len(state1.shape)-1 state1_T = th.transpose(state1, N, 0) overlap = th.tensordot(state1.T.conj(), state2, dims=N) return float(th.abs(overlap**2).flatten())
In [5]:
Copied!

    print("Initial overlap: ", overlap(psi_0, psi_0))

print("Initial overlap: ", overlap(psi_0, psi_0))

    Initial overlap:  1.0

    /tmp/ipykernel_601/4268046128.py:4: UserWarning: The use of `x.T` on tensors of dimension other than 2 to reverse their shape is deprecated and it will throw an error in a future release. Consider `x.mT` to transpose batches of matrices or `x.permute(*torch.arange(x.ndim - 1, -1, -1))` to reverse the dimensions of a tensor. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:3277.)
      overlap = th.tensordot(state1.T.conj(), state2, dims=N)

### Hamiltonian Evolution¶

In [6]:
Copied!

    from pyqtorch.core.operation import hamiltonian_evolution

from pyqtorch.core.operation import hamiltonian_evolution

Now let us define a simple Hamiltonian for the 4-qubits system, like a $\sigma_Z \otimes \sigma_Z$, in dense format as a $(N^2, N^2)$ tensor

In [7]:
Copied!

    sigmaz = th.diag(th.tensor([1.0, -1.0], dtype=th.cdouble))
    Hbase = th.kron(sigmaz, sigmaz)
    H = th.kron(Hbase, Hbase)
    H.shape

sigmaz = th.diag(th.tensor([1.0, -1.0], dtype=th.cdouble)) Hbase = th.kron(sigmaz, sigmaz) H = th.kron(Hbase, Hbase) H.shape
Out[7]:

    torch.Size([16, 16])

The overlap with itself should stay 1 after evolving for $t=0$, let's check this

In [8]:
Copied!

    t_evo = th.tensor([0], dtype=th.cdouble)
    psi = hamiltonian_evolution(H,
                        psi, t_evo,
                        range(N), N)

t_evo = th.tensor([0], dtype=th.cdouble) psi = hamiltonian_evolution(H, psi, t_evo, range(N), N)
In [9]:
Copied!

    print(f"Overlap after {t_evo} : ", overlap(psi, psi_0))

print(f"Overlap after {t_evo} : ", overlap(psi, psi_0))

    Overlap after tensor([0.+0.j], dtype=torch.complex128) :  1.0

Let's now evolve the state for a time $t = \pi/4$ and check that the overlap matches the expected value of 0.5.

In [10]:
Copied!

    t_evo = th.tensor([th.pi/4], dtype=th.cdouble)
    psi = hamiltonian_evolution(H,
                        psi, t_evo,
                        range(N), N
        )

t_evo = th.tensor([th.pi/4], dtype=th.cdouble) psi = hamiltonian_evolution(H, psi, t_evo, range(N), N )
In [11]:
Copied!

    print(f"Overlap after {t_evo} : ", overlap(psi, psi_0))

print(f"Overlap after {t_evo} : ", overlap(psi, psi_0))

    Overlap after tensor([0.7854+0.j], dtype=torch.complex128) :  0.50000000002474