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.)