# -*- coding: utf-8 -*-
""" Quantum State """
import ctypes
import random
import numpy as np
import qlazy.config as cfg
from qlazy.gpu import is_gpu_supported_lib
from qlazy.util import is_unitary_gate
from qlazy.QObject import QObject
[docs]class QState(ctypes.Structure, QObject):
""" Quantum State
Attributes
----------
qubit_num : int
qubit number of the quantum state (= log(state_num)).
state_num : int
dimension of the quantum state vector (= 2**qubit_num).
amp : list of complex
elements of the quantum state vector.
"""
if is_gpu_supported_lib() is True:
_fields_ = [
('qubit_num', ctypes.c_int),
('state_num', ctypes.c_int),
('buf_id', ctypes.c_int),
('camp', ctypes.c_void_p),
('buffer_0', ctypes.c_void_p),
('buffer_1', ctypes.c_void_p),
('prob_array', ctypes.c_void_p),
('prob_updated', ctypes.c_bool),
('d_buf_id', ctypes.c_int),
('d_camp', ctypes.c_void_p),
('d_buffer_0', ctypes.c_void_p),
('d_buffer_1', ctypes.c_void_p),
('d_prob_array', ctypes.c_void_p),
('d_prob_updated', ctypes.c_bool),
('gbank', ctypes.c_void_p),
('use_gpu', ctypes.c_bool),
]
else:
_fields_ = [
('qubit_num', ctypes.c_int),
('state_num', ctypes.c_int),
('buf_id', ctypes.c_int),
('camp', ctypes.c_void_p),
('buffer_0', ctypes.c_void_p),
('buffer_1', ctypes.c_void_p),
('prob_array', ctypes.c_void_p),
('prob_updated', ctypes.c_bool),
('gbank', ctypes.c_void_p),
('use_gpu', ctypes.c_bool),
]
def __new__(cls, qubit_num=0, vector=None, seed=None, use_gpu=False, **kwargs):
"""
Parameters
----------
qubit_num : int
qubit number of the quantum state.
vector : list
elements of the quantum state vector.
seed : int, default - set randomly
seed for random generation for meaurement.
use_gpu : bool
calcurate with GPU(cuda) or not.
Notes
-----
You must specify either 'qubit_num' or 'vector', not both.
"""
if seed is None:
seed = random.randint(0, 1000000)
# if qubit_num is not None:
if qubit_num > 0:
if qubit_num > cfg.MAX_QUBIT_NUM:
raise ValueError("qubit number must be {0:d} or less.".format(cfg.MAX_QUBIT_NUM))
obj = qstate_init(qubit_num, seed, use_gpu)
elif qubit_num == 0:
obj = qstate_init_with_vector(vector, seed, use_gpu)
else:
raise ValueError("qubit number must be positive.")
self = ctypes.cast(obj.value, ctypes.POINTER(cls)).contents
return self
def __str__(self):
return str(self.get_amp())
[docs] @classmethod
def add_method(cls, method):
"""
add method (custum gate).
Parameters
----------
method : func
method (custum gate) to add.
Examples
--------
>>> def bell(self, q0, q1):
>>> self.h(q0).cx(q0,q1)
>>> return self
>>> ...
>>> QState.add_method(bell)
>>> qs = QState(qubit_num=2)
>>> qs.bell(0,1)
>>> ...
"""
setattr(cls, method.__name__, method)
[docs] @classmethod
def add_methods(cls, *methods):
"""
add methods (custum gates).
Parameters
----------
methods : func, func, ...
arguments of methods (custum gates) to add.
Examples
--------
>>> def bell(self, q0, q1):
>>> self.h(q0).cx(q0,q1)
>>> return self
>>> ...
>>> def flip(self, q0, q1):
>>> self.x(q0).x(q1)
>>> return self
>>> ...
>>> QState.add_methods(bell, flip, ...)
>>> qs = QState(2)
>>> qs.bell(0,1)
>>> qs.flip(0,1)
>>> ...
"""
for method in methods:
if callable(method):
setattr(cls, method.__name__, method)
else:
raise ValueError("can't add method.")
[docs] @classmethod
def create_register(cls, num):
"""
create registers (qubit id's) and initialize zero.
Parameters
----------
num : int
qubit number you want to use.
Examples
--------
>>> qid = QState.create_register(3)
>>> print(qid)
[0,0,0]
"""
qid = [0]*num
return qid
[docs] @classmethod
def init_register(cls, *args):
"""
initialize registers (qubit id's).
Parameters
----------
args : list, list,...
arguments of qubit registers.
Returns
-------
idx : int
total qubit number.
Examples
--------
>>> qid_0 = QState.create_register(3)
>>> qid_1 = QState.create_register(2)
>>> print(qid_0, qid_1)
[0,0,0] [0,0]
>>> qnum = QState.init_register(qid_0, qid_1)
>>> print(qnum, qid_0, qid_1)
5 [0,1,2] [3,4]
"""
idx = 0
for i, arg in enumerate(args):
for j in range(len(arg)):
args[i][j] = idx
idx += 1
return idx
[docs] @classmethod
def del_all(cls, *qstates):
"""
free memory of the all quantum states.
Parameters
----------
qstates : instance of QState,instance of QState,...
set of QState instances
Returns
-------
None
"""
for qs in qstates:
if isinstance(qs, (list, tuple)):
cls.del_all(*qs)
elif isinstance(qs, cls):
del qs
else:
raise ValueError("can't free qstate.")
@property
def amp(self):
""" elements of quantum state vector. """
return self.get_amp()
[docs] def get_amp(self, qid=None):
"""
get the elements of quantum state vector.
Parameters
----------
qid : list of int, default - list of all of the qubit id
qubit id's list.
Returns
-------
ret : numpy.ndarray (complex)
elements of the quantum state vector.
Notes
-----
If 'qid' is set, specified qubit state are got after remaining
qubits are measured. So if the specified qubits are entangled
with the remaining qubits, output quantum state is
probabilistic. If no qubits are specified, you get all
elements of the quantum state.
"""
ret = qstate_get_camp(self, qid)
return ret
[docs] def get_prob(self, qid=None):
"""
get the probability list of quantum state vector.
Parameters
----------
qid : list of int, default - list of all of the qubit id
qubit id's list.
Returns
-------
prob : dict
key - bits string
value - probability
ex) {'00': 0.52, '11': 0.48}
"""
amp = qstate_get_camp(self, qid)
if qid is None:
digits = self.qubit_num
else:
digits = len(qid)
prob = {"{:0{digits}b}".format(i, digits=digits): abs(c) * abs(c)
for i, c in enumerate(amp) if abs(c) > cfg.EPS}
return prob
[docs] def partial(self, qid=None):
"""
get the partial quantum state.
Parameters
----------
qid : list of int, default - list of all of the qubit id
qubit id's list to get as a partial quantum
system.
Returns
-------
qs : instance of QState
partial quantum state.
Notes
-----
If 'qid' is set, specified partial quantum system are got
after remaining system are measured. So if the specified
quantum system are entangled with the remaining system, output
quantum state is probabilistic. If no qubits are specified,
you get the copy of original quantum state.
"""
vec = self.get_amp(qid)
qs = self.__class__(vector=vec)
return qs
[docs] def show(self, qid=None, nonzero=False, preal=0):
"""
show the quantum state
(elements of the state vector and probabilities).
Parameters
----------
qid : list of int, default - list of all of the qubit id
qubit id's list to show.
nonzero : bool, default False
if True, only non-zero amplitudes are printed.
preal : int, default 0
state id to make positive real amplitude.
(if -1 is set, do not go out the global phase factor)
Returns
-------
None
Notes
-----
If 'qid' is set, it shows the elements of quantum state vector
for partial quantum system specified by 'qid'. If the
specified quantum system are entangled with the remaining
system, output is probabilistic. If no qubits are specified,
it shows the whole quantum state. This method does not change
the original quantum state.
Examples
--------
>>> qs = QState(2).h(0).cx(0,1)
>>> qs.show()
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
...
>>> qs.show(qid=[0])
c[0] = +1.0000+0.0000*i : 1.0000 |+++++++++++
c[1] = +0.0000+0.0000*i : 0.0000 |
...
>>> qs.show(nonzero=True)
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
"""
if qid is None or len(qid) == 0:
digits = self.qubit_num
else:
digits = len(qid)
vec = self.get_amp(qid=qid)
if preal >= 0:
exp_i_phase = 1.+0.j
if abs(vec[preal].imag) > cfg.EPS:
exp_i_phase = vec[preal] / abs(vec[preal])
elif vec[preal].real < 0.0:
exp_i_phase = -exp_i_phase
vec = vec / exp_i_phase
for i, v in enumerate(vec):
bits = "{:0{digits}b}".format(i, digits=digits)
absval2 = abs(v) * abs(v)
if absval2 < cfg.EPS:
bar_len = 0
else:
bar_len = int(absval2 / 0.1 + 1.5)
bar_str = "|" + "+" * bar_len
if nonzero is True and absval2 < cfg.EPS:
continue
else:
print("c[{}] = {:+.4f}{:+.4f}*i : {:.4f} {}"
.format(bits, v.real, v.imag, abs(v)**2, bar_str))
[docs] def clone(self):
"""
get the copy of the quantum state.
Parameters
----------
None
Returns
-------
qstate : instance of QState
copy of the original quantum state.
"""
obj = qstate_copy(self)
qs = ctypes.cast(obj.value, ctypes.POINTER(self.__class__)).contents
return qs
[docs] def bloch(self, q=0):
"""
get bloch angles.
Parameters
----------
q : int
qubit id
Returns
-------
theta : float
bloch angle with Z-axis
phi : float
bloch angle with X-axis
Notes
-----
the unit of angle is PI radian. for example, 0.5 means 0.5*PI
(radian).
"""
theta, phi = qstate_bloch(self, q=q)
return theta, phi
[docs] def inpro(self, qstate, qid=None):
"""
get the inner product with quantum state.
Parameters
----------
qstate : instance of QState
one of the two quantum state.
qid : list of int, default - list of all of the qubit id
qubit id's list.
Returns
-------
inp : complex
inner produt (<self|qstate>).
Notes
-----
If 'qid' is set, you can get the inner product for partial
quantum state. If the specified quantum system are entangled
with the remaining system, output value is probabilistic,
while original quantum states do not change.
"""
if qid == [] or qid is None:
inp = qstate_inner_product(self, qstate)
else:
qs_0 = self.partial(qid=qid)
qs_1 = qstate.partial(qid=qid)
inp = qstate_inner_product(qs_0, qs_1)
return inp
[docs] def tenspro(self, qstate):
"""
get the tensor product with quantum state.
Parameters
----------
qstate : instance of QState
quantum state to get the tensor product.
Returns
-------
qstate_out : instance of QState
tensor produt of 'self' and 'qstate'.
"""
obj = qstate_tensor_product(self, qstate)
qs = ctypes.cast(obj.value, ctypes.POINTER(self.__class__)).contents
return qs
[docs] def fidelity(self, qstate, qid=None):
"""
get fidelity with the quantum state.
Parameters
----------
qstate : instance of QState
one of the two quantum states.
qid : list of int
qubit id's list.
Returns
-------
fid : float
fidelity of two quantum states. absolute value of the
inner product of two quantum states.
Notes
-----
If 'qid' is set, you can get the fidelity for partial quantum
state. If the specified quantum system are entangled with the
remaining system, output value is probabilistic, while
original quantum states do not change.
"""
return abs(self.inpro(qstate, qid=qid))
[docs] def composite(self, num=1):
"""
get the composite state of same quantum states.
Parameters
----------
num : int
number of quantum states.
Returns
-------
qs : instance of QState
composite quantum state.
"""
if num <= 1:
return self
qs = self.clone()
for _ in range(num-1):
qs_tmp = qs.tenspro(self)
qs = qs_tmp.clone()
return qs
[docs] def join(self, qs_list):
"""
get tensor product state of the quantum states' list.
Parameters
----------
qs_list : list (QState)
list of quantum states.
Returns
-------
qs_out : instance of QState
tensor product state.
"""
qs_out = self.clone()
for qs in qs_list:
qs_tmp = qs_out.clone()
qs_out = qs_tmp.tenspro(qs)
return qs_out
[docs] def evolve(self, observable=None, time=0.0, iteration=0):
"""
evolve the quantum state.
Parameters
----------
observable : instance of Observable
Hamiltonian of the system.
time : float
period of time.
iter : int
number of iteration.
Returns
-------
self : instance of QState
Notes
-----
The 'iter' value should be sufficiently larger than the
'time' value. This method change the original state.
See Also
--------
Obserbable class (Observable.py)
"""
ob = observable.clone()
if ob.recalc_weight() is False:
raise ValueError("Observable is not hermitian.")
qstate_evolve(self, observable=ob.base, time=time, iteration=iteration)
return self
[docs] def expect(self, observable=None):
"""
get the expectation value for observable under the quantum state.
Parameters
----------
observable : instance of Observable
obserbable of the system.
Returns
-------
expect : float
expect value.
See Also
--------
Obserbable class (Observable.py)
"""
ob = observable.clone()
if ob.recalc_weight() is False:
raise ValueError("Observable is not hermitian.")
if self.qubit_num < ob.qubit_num:
raise ValueError("total qubit number of the observable must be less than qstate's.")
expect = qstate_expect_value(self, observable=ob.base)
return expect
[docs] def apply(self, matrix=None, qid=None):
"""
apply matrix.
Parameters
----------
matrix : list of list
matrix to apply.
Returns
-------
self : instance of QState
Notes
-----
If 'qid' isn't set, dimension of the matrix must be equal to
the 2 power of qubit number of the system. If 'qid' is set,
dimension of the matrix must be equal to the 2 power of 'qid'
length.
"""
qstate_apply_matrix(self, matrix=matrix, qid=qid)
return self
def __schmidt_decomp(self, qid_0=None, qid_1=None):
if qid_0 is None:
qid_0 = []
if qid_1 is None:
qid_1 = []
vec = self.get_amp(qid=qid_0+qid_1)
row = 2**len(qid_0)
col = 2**len(qid_1)
mat = np.zeros((row, col), dtype=complex)
for idx, comp in enumerate(vec):
mat[idx//col][idx%col] = comp
U, D, V = np.linalg.svd(mat, full_matrices=False)
coef = np.array([d for d in D if d > cfg.EPS])
vec_0 = [v for i, v in enumerate(U.T) if i < len(coef)]
vec_1 = [v for i, v in enumerate(V) if i < len(coef)]
return (coef, vec_0, vec_1)
[docs] def schmidt_decomp(self, qid_0=None, qid_1=None):
"""
schmidt decomposition.
Parameters
----------
qid_0 : list
subsystem to decomposite.
qid_1 : list
another subsystem to decomposite.
Returns
-------
coef : numpy.ndarray
schmidt coefficients.
qs_0 : list of QState instances
decomposite quantum state related to 'qid_0'.
qs_1 : list of QState instances
decomposite quantum state related to 'qid_1'.
"""
coef, vec_0, vec_1 = self.__schmidt_decomp(qid_0=qid_0, qid_1=qid_1)
qs_0 = [self.__class__(vector=v) for i, v in enumerate(vec_0) if i < len(coef)]
qs_1 = [self.__class__(vector=v) for i, v in enumerate(vec_1) if i < len(coef)]
return (coef, qs_0, qs_1)
[docs] def schmidt_coef(self, qid_0=None, qid_1=None):
"""
get schmidt coefficients.
Parameters
----------
qid_0 : list
subsystem to decomposite.
qid_1 : list
another subsystem to decomposite.
Returns
-------
coef : numpy.ndarray
schmidt coefficients.
"""
coef = self.__schmidt_decomp(qid_0=qid_0, qid_1=qid_1)[0]
return coef
# measurement
[docs] def measure(self, qid=None):
"""
one shot measurement in Z-direction.
Parameters
----------
qid : list of int
qubit id list to measure.
Returns
-------
mval : str
measurement value.
Examples
--------
>>> qs = QState(qubit_num=2).h(0).cx(0,1)
>>> qs.show()
>>> print(qs.measure(qid=[0,1]))
>>> qs.show()
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
00
c[00] = +1.0000+0.0000*i : 1.0000 |+++++++++++
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +0.0000+0.0000*i : 0.0000 |
"""
mval = qstate_measure(self, qid=qid)
return mval
[docs] def m(self, qid=None, shots=cfg.DEF_SHOTS, angle=0.0, phase=0.0):
"""
measurement in any direction (default: Z-axis).
Parameters
----------
qid : list of int
qubit id list to measure.
shots : int, default 1
number of measurements.
angle : float, default 0.0
direction of measurement (angle with Z-axis).
phase : float, default 0.0
direction of measurement (phase around Z-axis).
Returns
-------
md : instance of MData
measurement data.
Examples
--------
>>> qs = QState(2).h(0).cx(0,1)
>>> md = qs.m()
>>> md.show(shots=100)
direction of measurement: z-axis
frq[00] = 52
frq[11] = 48
last state => 11
See Also
--------
MData class (MData.py)
"""
md = qstate_measure_stats(self, qid=qid, shots=shots,
angle=angle, phase=phase)
return md
[docs] def mx(self, qid=None, shots=cfg.DEF_SHOTS):
"""
X-axis measurement.
Parameters
----------
qid : list of int
qubit id list to measure.
shots : int, default 1
number of measurements.
Returns
-------
md : instance of MData
measurement data.
See Also
--------
MData class (MData.py)
"""
md = qstate_measure_stats(self, qid=qid, shots=shots,
angle=0.5, phase=0.0)
return md
[docs] def my(self, qid=None, shots=cfg.DEF_SHOTS):
"""
Y-axis measurement.
Parameters
----------
qid : list of int
qubit id list to measure.
shots : int, default 1
number of measurements.
Returns
-------
md : instance of MData
measurement data.
See Also
--------
MData class (MData.py)
"""
md = qstate_measure_stats(self, qid=qid, shots=shots,
angle=0.5, phase=0.5)
return md
[docs] def mz(self, qid=None, shots=cfg.DEF_SHOTS):
"""
Z-axis measurement.
Parameters
----------
qid : list of int
qubit id list to measure.
shots : int, default 1
number of measurements.
Returns
-------
md : instance of MData
measurement data.
See Also
--------
MData class (MData.py)
"""
md = qstate_measure_stats(self, qid=qid, shots=shots,
angle=0.0, phase=0.0)
return md
[docs] def mb(self, qid=None, shots=cfg.DEF_SHOTS):
""" bell measurement """
return qstate_measure_bell_stats(self, qid=qid, shots=shots)
# operate gate
[docs] def operate_gate(self, kind=None, qid=None, phase=0.0, **kwargs):
"""
operate gate
Parameters
----------
kind : int
kind of the gate
qid : list
quantum id list
phase : float
phase for rotation gate
Returns
-------
self : instance of QState
quantum state
"""
if kind == cfg.RESET:
qstate_reset(self, qid=qid)
elif is_unitary_gate(kind):
qstate_operate_qgate(self, kind=kind, qid=qid, phase=phase)
else:
raise ValueError("gate: {} is not supported.".format(cfg.GATE_STRING[kind]))
return self
# operate quantum circuit
[docs] def operate_qcirc(self, qcirc, qctrl=None):
"""
operate quantum circuit
Parameters
----------
qcirc : instance of QCirc
quantum circuit
qctrl : int
control qubit id
Returns
-------
self : instance of QState
quantum state after executing the quantum circuit
Notes
-----
The quantum circut must be unintary.
"""
if qcirc.is_unitary() is False:
raise ValueError("qcirc must be unitary quantum circuit.")
if self.qubit_num < qcirc.qubit_num:
raise ValueError("qubit number of quantum state must be equal or larger than the quantum circuit size.")
if qctrl is None:
qstate_operate_qcirc(self, cmem=None, qcirc=qcirc, shots=1, cid=None, out_state=True)
else:
qc_qctrl = qcirc.add_control(qctrl=qctrl)
qstate_operate_qcirc(self, cmem=None, qcirc=qc_qctrl, shots=1, cid=None, out_state=True)
return self
def __del__(self):
qstate_free(self)
# c-library for qstate
from qlazy.lib.qstate_c import (qstate_init, qstate_init_with_vector, qstate_reset,
qstate_print, qstate_copy, qstate_bloch,
qstate_inner_product, qstate_get_camp,
qstate_tensor_product, qstate_evolve, qstate_expect_value,
qstate_apply_matrix, qstate_operate_qgate, qstate_measure,
qstate_measure_stats, qstate_measure_bell_stats, qstate_operate_qcirc,
qstate_free)