Circuit Basics#

Overview#

In this note, we will learn about basic operations of the core object in TensorCircuit - tc.Circuit which supports both noiseless simulation and noisy simulation with the Monte Carlo trajectory-based method. More importantly, near all the operations on the Circuit object is differentiable and jittable, which is the key for successful and efficient variational quantum algorithm simulations.

[WIP note]

Setup#

[1]:
from functools import partial
import inspect
import sys
import numpy as np
import tensorflow as tf

import tensorcircuit as tc

Hello world example#

[2]:
def get_circuit(n):
    c = tc.Circuit(n)  # initialize a circuit object with n qubits
    for i in range(n):
        c.H(i)  # apply Hadamard gate on each qubit
    c.cnot(0, 1)  # apply cnot with control qubit on 0-th qubit
    c.CNOT(n - 1, n - 2)  # capitalized API also works
    return c
[3]:
# print possible gates without parameters
print(tc.Circuit.sgates)
['i', 'x', 'y', 'z', 'h', 't', 's', 'td', 'sd', 'wroot', 'cnot', 'cz', 'swap', 'cy', 'iswap', 'ox', 'oy', 'oz', 'toffoli', 'fredkin']
[4]:
# the corresponding matrix for these gates definition
for g in tc.Circuit.sgates:
    gf = getattr(tc.gates, g)
    print(g)
    print(tc.gates.matrix_for_gate(gf()))
i
[[1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j]]
x
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]
y
[[0.+0.j 0.-1.j]
 [0.+1.j 0.+0.j]]
z
[[ 1.+0.j  0.+0.j]
 [ 0.+0.j -1.+0.j]]
h
[[ 0.70710677+0.j  0.70710677+0.j]
 [ 0.70710677+0.j -0.70710677+0.j]]
t
[[1.        +0.j         0.        +0.j        ]
 [0.        +0.j         0.70710677+0.70710677j]]
s
[[1.+0.j 0.+0.j]
 [0.+0.j 0.+1.j]]
td
[[1.        +0.j         0.        +0.j        ]
 [0.        +0.j         0.70710677-0.70710677j]]
sd
[[1.+0.j 0.+0.j]
 [0.+0.j 0.-1.j]]
wroot
[[ 0.70710677+0.j  -0.5       -0.5j]
 [ 0.5       -0.5j  0.70710677+0.j ]]
cnot
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]]
cz
[[ 1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  1.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j -1.+0.j]]
swap
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
cy
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.-1.j]
 [0.+0.j 0.+0.j 0.+1.j 0.+0.j]]
iswap
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+1.j 0.+0.j]
 [0.+0.j 0.+1.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
ox
[[0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
oy
[[0.+0.j 0.-1.j 0.+0.j 0.+0.j]
 [0.+1.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
oz
[[ 1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j -1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  1.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j  1.+0.j]]
toffoli
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j]]
fredkin
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
[5]:
c = get_circuit(3)
ir = c.to_qir()  # intermediate representation of the circuit
ir
[5]:
[{'gatef': h,
  'gate': Gate(
      name: 'h',
      tensor:
          array([[ 0.70710677+0.j,  0.70710677+0.j],
                 [ 0.70710677+0.j, -0.70710677+0.j]], dtype=complex64),
      edges: [
          Edge('cnot'[2] -> 'h'[0] ),
          Edge('h'[1] -> 'qb-1'[0] )
      ]),
  'index': (0,),
  'name': 'h',
  'split': None,
  'mpo': False},
 {'gatef': h,
  'gate': Gate(
      name: 'h',
      tensor:
          array([[ 0.70710677+0.j,  0.70710677+0.j],
                 [ 0.70710677+0.j, -0.70710677+0.j]], dtype=complex64),
      edges: [
          Edge('cnot'[3] -> 'h'[0] ),
          Edge('h'[1] -> 'qb-2'[0] )
      ]),
  'index': (1,),
  'name': 'h',
  'split': None,
  'mpo': False},
 {'gatef': h,
  'gate': Gate(
      name: 'h',
      tensor:
          array([[ 0.70710677+0.j,  0.70710677+0.j],
                 [ 0.70710677+0.j, -0.70710677+0.j]], dtype=complex64),
      edges: [
          Edge('cnot'[2] -> 'h'[0] ),
          Edge('h'[1] -> 'qb-3'[0] )
      ]),
  'index': (2,),
  'name': 'h',
  'split': None,
  'mpo': False},
 {'gatef': cnot,
  'gate': Gate(
      name: 'cnot',
      tensor:
          array([[[[1.+0.j, 0.+0.j],
                   [0.+0.j, 0.+0.j]],

                  [[0.+0.j, 1.+0.j],
                   [0.+0.j, 0.+0.j]]],


                 [[[0.+0.j, 0.+0.j],
                   [0.+0.j, 1.+0.j]],

                  [[0.+0.j, 0.+0.j],
                   [1.+0.j, 0.+0.j]]]], dtype=complex64),
      edges: [
          Edge(Dangling Edge)[0],
          Edge('cnot'[3] -> 'cnot'[1] ),
          Edge('cnot'[2] -> 'h'[0] ),
          Edge('cnot'[3] -> 'h'[0] )
      ]),
  'index': (0, 1),
  'name': 'cnot',
  'split': None,
  'mpo': False},
 {'gatef': cnot,
  'gate': Gate(
      name: 'cnot',
      tensor:
          array([[[[1.+0.j, 0.+0.j],
                   [0.+0.j, 0.+0.j]],

                  [[0.+0.j, 1.+0.j],
                   [0.+0.j, 0.+0.j]]],


                 [[[0.+0.j, 0.+0.j],
                   [0.+0.j, 1.+0.j]],

                  [[0.+0.j, 0.+0.j],
                   [1.+0.j, 0.+0.j]]]], dtype=complex64),
      edges: [
          Edge(Dangling Edge)[0],
          Edge(Dangling Edge)[1],
          Edge('cnot'[2] -> 'h'[0] ),
          Edge('cnot'[3] -> 'cnot'[1] )
      ]),
  'index': (2, 1),
  'name': 'cnot',
  'split': None,
  'mpo': False}]
[6]:
ir[0]["gatef"]().tensor, ir[-1]["gate"].tensor  # the actually unitary for each gate
[6]:
(array([[ 0.70710677+0.j,  0.70710677+0.j],
        [ 0.70710677+0.j, -0.70710677+0.j]], dtype=complex64),
 array([[[[1.+0.j, 0.+0.j],
          [0.+0.j, 0.+0.j]],

         [[0.+0.j, 1.+0.j],
          [0.+0.j, 0.+0.j]]],


        [[[0.+0.j, 0.+0.j],
          [0.+0.j, 1.+0.j]],

         [[0.+0.j, 0.+0.j],
          [1.+0.j, 0.+0.j]]]], dtype=complex64))
[7]:
# compute the final output quantum state from the circuit
c.state()
[7]:
array([0.35355335+0.j, 0.35355335+0.j, 0.35355335+0.j, 0.35355335+0.j,
       0.35355335+0.j, 0.35355335+0.j, 0.35355335+0.j, 0.35355335+0.j],
      dtype=complex64)
[8]:
# compute some expectation values, say <X1>
x1 = c.expectation([tc.gates.x(), [1]])

# or <Z1Z2>
z1z2 = c.expectation([tc.gates.z(), [1]], [tc.gates.z(), [2]])

print(x1, z1z2)
(0.9999998+0j) 0j
[9]:
# make some random samples
for _ in range(10):
    print(c.perfect_sampling())
(array([0., 0., 0.], dtype=float32), 0.12499997764825821)
(array([1., 1., 0.], dtype=float32), 0.1249999776482098)
(array([1., 1., 0.], dtype=float32), 0.1249999776482098)
(array([0., 1., 0.], dtype=float32), 0.12499997764825821)
(array([1., 0., 0.], dtype=float32), 0.12499997019766829)
(array([0., 0., 1.], dtype=float32), 0.12499997764825821)
(array([1., 1., 1.], dtype=float32), 0.1250001713634208)
(array([1., 0., 0.], dtype=float32), 0.12499997019766829)
(array([0., 1., 1.], dtype=float32), 0.12499997764825821)
(array([1., 0., 1.], dtype=float32), 0.12499997019766829)
[10]:
# we can easily switch simulation backends away from NumPy!

with tc.runtime_backend("tensorflow") as K:
    c = get_circuit(3)
    print(c.state())

with tc.runtime_backend("jax") as K:
    c = get_circuit(3)
    print(c.state())

with tc.runtime_backend("pytorch") as K:
    # best performance and full functionality are not guaranteed on pytorch backend
    c = get_circuit(3)
    print(c.state())
tf.Tensor(
[0.35355335+0.j 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j
 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j], shape=(8,), dtype=complex64)
WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
[0.35355335+0.j 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j
 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j 0.35355335+0.j]
tensor([0.3536+0.j, 0.3536+0.j, 0.3536+0.j, 0.3536+0.j, 0.3536+0.j, 0.3536+0.j, 0.3536+0.j,
        0.3536+0.j])

Parameterized Quantum Circuit (PQC)#

[11]:
# circuit gates that accept parameters

print(tc.Circuit.vgates)
['r', 'cr', 'rx', 'ry', 'rz', 'crx', 'cry', 'crz', 'orx', 'ory', 'orz', 'any', 'exp', 'exp1']
[12]:
# see the keyword parameters (with type float) for each type of variable gate
for g in tc.Circuit.vgates:
    print(g, inspect.signature(getattr(tc.gates, g).f))
r (theta: float = 0, alpha: float = 0, phi: float = 0) -> tensorcircuit.gates.Gate
cr (theta: float = 0, alpha: float = 0, phi: float = 0) -> tensorcircuit.gates.Gate
rx (theta: float = 0) -> tensorcircuit.gates.Gate
ry (theta: float = 0) -> tensorcircuit.gates.Gate
rz (theta: float = 0) -> tensorcircuit.gates.Gate
crx (*args: Any, **kws: Any) -> Any
cry (*args: Any, **kws: Any) -> Any
crz (*args: Any, **kws: Any) -> Any
orx (*args: Any, **kws: Any) -> Any
ory (*args: Any, **kws: Any) -> Any
orz (*args: Any, **kws: Any) -> Any
any (unitary: Any, name: str = 'any') -> tensorcircuit.gates.Gate
exp (unitary: Any, theta: float, name: str = 'none') -> tensorcircuit.gates.Gate
exp1 (unitary: Any, theta: float, name: str = 'none') -> tensorcircuit.gates.Gate
[13]:
def get_circuit(n, params):
    c = tc.Circuit(n)  # initialize a circuit object with n qubits
    for i in range(n):
        c.rx(i, theta=params[i])  # apply rx gate
    c.cnot(0, 1)
    return c
[14]:
K = tc.set_backend("tensorflow")
[15]:
n = 3
params = K.ones([n])
c = get_circuit(n, params)
print(c.state())
tf.Tensor(
[ 0.6758712 +0.j          0.        -0.36923012j  0.        -0.36923015j
 -0.20171136-0.j         -0.20171136+0.j          0.        +0.11019541j
  0.        -0.36923015j -0.20171136-0.j        ], shape=(8,), dtype=complex64)
[16]:
ir = c.to_qir()
ir
[16]:
[{'gatef': rx,
  'index': (0,),
  'name': 'rx',
  'split': None,
  'mpo': False,
  'parameters': {'theta': <tf.Tensor: shape=(), dtype=complex64, numpy=(1+0j)>},
  'gate': Gate(
      name: '__unnamed_node__',
      tensor:
          <tf.Tensor: shape=(2, 2), dtype=complex64, numpy=
          array([[0.87758255+0.j        , 0.        -0.47942555j],
                 [0.        -0.47942555j, 0.87758255+0.j        ]], dtype=complex64)>,
      edges: [
          Edge('cnot'[2] -> '__unnamed_node__'[0] ),
          Edge('__unnamed_node__'[1] -> 'qb-1'[0] )
      ])},
 {'gatef': rx,
  'index': (1,),
  'name': 'rx',
  'split': None,
  'mpo': False,
  'parameters': {'theta': <tf.Tensor: shape=(), dtype=complex64, numpy=(1+0j)>},
  'gate': Gate(
      name: '__unnamed_node__',
      tensor:
          <tf.Tensor: shape=(2, 2), dtype=complex64, numpy=
          array([[0.87758255+0.j        , 0.        -0.47942555j],
                 [0.        -0.47942555j, 0.87758255+0.j        ]], dtype=complex64)>,
      edges: [
          Edge('cnot'[3] -> '__unnamed_node__'[0] ),
          Edge('__unnamed_node__'[1] -> 'qb-2'[0] )
      ])},
 {'gatef': rx,
  'index': (2,),
  'name': 'rx',
  'split': None,
  'mpo': False,
  'parameters': {'theta': <tf.Tensor: shape=(), dtype=complex64, numpy=(1+0j)>},
  'gate': Gate(
      name: '__unnamed_node__',
      tensor:
          <tf.Tensor: shape=(2, 2), dtype=complex64, numpy=
          array([[0.87758255+0.j        , 0.        -0.47942555j],
                 [0.        -0.47942555j, 0.87758255+0.j        ]], dtype=complex64)>,
      edges: [
          Edge(Dangling Edge)[0],
          Edge('__unnamed_node__'[1] -> 'qb-3'[0] )
      ])},
 {'gatef': cnot,
  'gate': Gate(
      name: 'cnot',
      tensor:
          <tf.Tensor: shape=(2, 2, 2, 2), dtype=complex64, numpy=
          array([[[[1.+0.j, 0.+0.j],
                   [0.+0.j, 0.+0.j]],

                  [[0.+0.j, 1.+0.j],
                   [0.+0.j, 0.+0.j]]],


                 [[[0.+0.j, 0.+0.j],
                   [0.+0.j, 1.+0.j]],

                  [[0.+0.j, 0.+0.j],
                   [1.+0.j, 0.+0.j]]]], dtype=complex64)>,
      edges: [
          Edge(Dangling Edge)[0],
          Edge(Dangling Edge)[1],
          Edge('cnot'[2] -> '__unnamed_node__'[0] ),
          Edge('cnot'[3] -> '__unnamed_node__'[0] )
      ]),
  'index': (0, 1),
  'name': 'cnot',
  'split': None,
  'mpo': False}]
[17]:
# see the gate unitary
ir[0]["gatef"](**ir[0]["parameters"]).tensor
[17]:
<tf.Tensor: shape=(2, 2), dtype=complex64, numpy=
array([[0.87758255+0.j        , 0.        -0.47942555j],
       [0.        -0.47942555j, 0.87758255+0.j        ]], dtype=complex64)>
[18]:
# let us compose a differentiable quantum function


def energy(params):
    c = get_circuit(n, params)
    return K.real(c.expectation([tc.gates.z(), [1]]))


energy_vag = K.value_and_grad(energy)

print(energy_vag(params))

# once we have the gradient, we can run gradient-based descent for variational optimization
(<tf.Tensor: shape=(), dtype=float32, numpy=0.2919265>, <tf.Tensor: shape=(3,), dtype=complex64, numpy=
array([-4.5464873e-01+0.j, -4.5464873e-01+0.j,  2.2351742e-08+0.j],
      dtype=complex64)>)
[19]:
# and jit it for acceleration!

energy_vag_jit = K.jit(K.value_and_grad(energy))

print(energy_vag_jit(params))
# the first time to run a jitted function will be slow, but the following evaluation will be super fast
(<tf.Tensor: shape=(), dtype=float32, numpy=0.2919265>, <tf.Tensor: shape=(3,), dtype=complex64, numpy=
array([-4.5464873e-01+0.j, -4.5464873e-01+0.j,  2.2351742e-08+0.j],
      dtype=complex64)>)

Advances for Circuit#

Input State#

We can replace the input state from the default |0^n>

[20]:
input_state = K.ones([2**n])
input_state /= K.norm(input_state)

c = tc.Circuit(n, inputs=input_state)
c.H(0)
c.state()
[20]:
<tf.Tensor: shape=(8,), dtype=complex64, numpy=
array([0.49999997+0.j, 0.49999997+0.j, 0.49999997+0.j, 0.49999997+0.j,
       0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j],
      dtype=complex64)>

Monte Carlo Noise Simulation#

tc.Circuit supports noisy simulation using the Monte Carlo method, and it is also jittable! Besides, tc.DMCircuit supports noisy simulation using the full density matrix method.

[21]:
c = tc.Circuit(n)
for i in range(n):
    c.H(i)
for i in range(n - 1):
    c.cnot(i, i + 1)
    c.depolarizing(i, px=0.1, py=0.1, pz=0.1)
    c.apply_general_kraus(tc.channels.phasedampingchannel(gamma=0.2), i + 1)
print(c.expectation([tc.gates.y(), [1]]))
tf.Tensor(0j, shape=(), dtype=complex64)

Apply Arbitrary Gate#

Just directly using any API by feeding the corresponding unitary

[22]:
c = tc.Circuit(n)
c.any(0, 1, unitary=K.ones([4, 4]) / K.norm(K.ones([4, 4])))
c.state()
[22]:
<tf.Tensor: shape=(8,), dtype=complex64, numpy=
array([0.25+0.j, 0.  +0.j, 0.25+0.j, 0.  +0.j, 0.25+0.j, 0.  +0.j,
       0.25+0.j, 0.  +0.j], dtype=complex64)>

Exponential Gate#

If we want to simulate gate as \(e^{i\theta G}\) where \(G^2=1\) is a matrix, we have a fast and efficient implementation for such gates as exp1

[23]:
c = tc.Circuit(n)
for i in range(n):
    c.H(i)
for i in range(n - 1):
    c.exp1(i, i + 1, theta=K.ones([]), unitary=tc.gates._zz_matrix)
c.state()
[23]:
<tf.Tensor: shape=(8,), dtype=complex64, numpy=
array([-0.14713009-3.2148516e-01j,  0.35355335+1.4901161e-08j,
       -0.14713009+3.2148516e-01j,  0.35355335-1.4901161e-08j,
        0.35355335-1.4901161e-08j, -0.14713009+3.2148516e-01j,
        0.35355335+1.4901161e-08j, -0.14713009-3.2148516e-01j],
      dtype=complex64)>

In the above example \(G=Z\otimes Z\)

[24]:
print(tc.gates._zz_matrix)
[[ 1.  0.  0.  0.]
 [ 0. -1.  0. -0.]
 [ 0.  0. -1. -0.]
 [ 0. -0. -0.  1.]]

Common matrices in gates modules are listed below

[25]:
for name in dir(tc.gates):
    if name.endswith("_matrix"):
        print(name, ":\n", getattr(tc.gates, name))
_cnot_matrix :
 [[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]]
_cy_matrix :
 [[ 1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j -0.-1.j]
 [ 0.+0.j  0.+0.j  0.+1.j  0.+0.j]]
_cz_matrix :
 [[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  1.  0.]
 [ 0.  0.  0. -1.]]
_fredkin_matrix :
 [[1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]]
_h_matrix :
 [[ 0.70710678  0.70710678]
 [ 0.70710678 -0.70710678]]
_i_matrix :
 [[1. 0.]
 [0. 1.]]
_ii_matrix :
 [[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]
_s_matrix :
 [[1.+0.j 0.+0.j]
 [0.+0.j 0.+1.j]]
_swap_matrix :
 [[1. 0. 0. 0.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]]
_t_matrix :
 [[1.        +0.j         0.        +0.j        ]
 [0.        +0.j         0.70710678+0.70710678j]]
_toffoli_matrix :
 [[1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 1. 0.]]
_wroot_matrix :
 [[ 0.70710678+0.j  -0.5       -0.5j]
 [ 0.5       -0.5j  0.70710678+0.j ]]
_x_matrix :
 [[0. 1.]
 [1. 0.]]
_xx_matrix :
 [[0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]
 [1. 0. 0. 0.]]
_y_matrix :
 [[ 0.+0.j -0.-1.j]
 [ 0.+1.j  0.+0.j]]
_yy_matrix :
 [[ 0.+0.j  0.-0.j  0.-0.j -1.+0.j]
 [ 0.+0.j  0.+0.j  1.-0.j  0.-0.j]
 [ 0.+0.j  1.-0.j  0.+0.j  0.-0.j]
 [-1.+0.j  0.+0.j  0.+0.j  0.+0.j]]
_z_matrix :
 [[ 1.  0.]
 [ 0. -1.]]
_zz_matrix :
 [[ 1.  0.  0.  0.]
 [ 0. -1.  0. -0.]
 [ 0.  0. -1. -0.]
 [ 0. -0. -0.  1.]]

Non-unitary Gate#

tc.Circuit automatically support non-unitary circuit simulation due to its TensorNetwork engine nature

[26]:
c = tc.Circuit(n)
c.exp1(1, unitary=tc.gates._x_matrix, theta=K.ones([]) + 1.0j * K.ones([]))
c.state()
[26]:
<tf.Tensor: shape=(8,), dtype=complex64, numpy=
array([0.83373   -0.9888977j, 0.        +0.j       ,
       0.63496387-1.2984576j, 0.        +0.j       ,
       0.        +0.j       , 0.        +0.j       ,
       0.        +0.j       , 0.        +0.j       ], dtype=complex64)>

Note in this case the final state is not normalized anymore

[27]:
try:
    np.testing.assert_allclose(K.norm(c.state()), 1.0)
except AssertionError as e:
    print(e)

Not equal to tolerance rtol=1e-07, atol=0

Mismatched elements: 1 / 1 (100%)
Max absolute difference: 0.93963802
Max relative difference: 0.93963802
 x: array(1.939638+0.j, dtype=complex64)
 y: array(1.)