import qubiter.utilities_gen as ut
from qubiter.SEO_writer import *
from qubiter.HadamardTransform import *
from qubiter.SEO_MatrixProduct import *
[docs]class DiagUnitarySEO_writer(SEO_writer):
"""
A diagonal unitary (d-unitary) is square diagonal matrix whose diagonal
entries are magnitude 1 complex numbers. Any d-unitary gate can be
represent in an English file by a single line starting with DIAG.
This class is a child of SEO_writer. It adds to its parent class the
ability to write a d-unitary gate in various styles.
When style = 'one_line', this class writes a d-unitary as a single line
in an English file, the same away the parent class SEO_writer would
write it.
When style = 'exact', this class writes a d-unitary as a SEO expansion
occupying multiple lines of an English file. For this style, the class
writes an exact expansion described in Refs. 1 and 2 below.
Actually, this class can write more than a mere d-unitary. It can write
a controlled d-unitary too, meaning it can attach T or F controls to the
intrinsic controls of the d-unitary. Those intrinsic controls are
represented by percent signs in Picture files and by "half moon" nodes
in the arxiv papers cited below. In class Controls, T controls are of
kind True, F controls are of kind False, and intrinsic controls are
assigned an int for kind.
It is important to note that the bits in the d-unitary being written
will be in the following order, in order of increasing bit position:
1. T controls
2. F controls
3. intrinsic d-unitary controls
4. grounded bits, if any
References
----------
1. R.R. Tucci, A Rudimentary Quantum Compiler(2cnd Ed.)
https://arxiv.org/abs/quant-ph/9902062
2. R.R. Tucci, How to Compile Some NAND Formula Evaluators,
https://arxiv.org/abs/0706.0479
3. R.R. Tucci, Oracular Approximation of Quantum Multiplexors and
Diagonal Unitary Matrices, https://arxiv.org/abs/0901.3851
Attributes
----------
num_of_F_trols : int
The number of False controls of the controlled d-unitary.
num_gbits : int
number of gbits (grounded bits). This is only needed for the
oracular approximation. Grounded bits are extra ancilla bits that
have been initialized to the ground state (state ``|0>``).
num_of_T_trols : int
The number of True controls of the controlled d-unitary.
rad_angles : list(float)
list of angles in radians. These angles are the parameters
specifying an DIAG gate. If the DIAG has N intrinsic controls,
there are 2^N angles.
style : str
must equal either 'one_line' or exact'.
"""
[docs] def __init__(self, file_prefix, emb, style, rad_angles=None,
num_T_trols=0, num_F_trols=0, num_gbits=0, **kwargs):
"""
Constructor
Parameters
----------
file_prefix : str
emb : CktEmbedder
style : str
rad_angles : list(float)
num_T_trols : int
num_F_trols : int
num_gbits : int
Returns
-------
"""
self.style = style
self.rad_angles = rad_angles
if rad_angles:
self.rad_angles = rad_angles
self.num_T_trols = num_T_trols
self.num_F_trols = num_F_trols
self.num_gbits = 0
if style == 'oracular':
self.num_gbits = num_gbits
num_qbits = emb.num_qbits_bef
assert num_qbits >= 1, "d-unitary must have at least 1 qubit"
ntf = num_T_trols + num_F_trols
num_MP_trols = num_qbits - ntf - num_gbits
assert num_MP_trols > 0
if rad_angles:
assert len(rad_angles) == (1 << num_MP_trols), \
"wrong number of d-unitary angles"
SEO_writer.__init__(self, file_prefix, emb, **kwargs)
[docs] def write_one_line(self):
"""
Writes in English file a one line representation of the d-unitary.
Returns
-------
None
"""
num_qbits = self.emb.num_qbits_bef
nt = self.num_T_trols
nf = self.num_F_trols
ntf = nt + nf
num_MP_trols = num_qbits - ntf - self.num_gbits
trols = Controls(num_qbits)
trols.bit_pos_to_kind = dict(enumerate(
[True]*nt + [False]*nf + list(range(num_MP_trols))
))
trols.refresh_lists()
self.write_controlled_diag_unitary_gate(trols, self.rad_angles)
[docs] def write_exact(self):
"""
Writes in English file a multiple line, exact representation of the
d-unitary.
Returns
-------
None
"""
num_qbits = self.emb.num_qbits_bef
nt = self.num_T_trols
nf = self.num_F_trols
ntf = nt + nf
num_MP_trols = num_qbits - ntf - self.num_gbits
rads_arr = np.array(self.rad_angles)
if np.linalg.norm(rads_arr) < 1e-6:
print("unit d-unitary")
return
conj_rads = HadamardTransform.ht(num_MP_trols, rads_arr)
num_factors = (1 << num_MP_trols)
f, lazy = BitVector.lazy_advance(0, 0) # start at f=1
cur_rot_bpos = 0
prev_rot_bpos = 0
cur_bvec = BitVector(num_MP_trols+1, 1) # start at 1
prev_bvec = BitVector(num_MP_trols+1, 0)
diff_bvec = BitVector(num_MP_trols+1, 0)
TF_dict = dict(enumerate([True]*nt + [False]*nf))
trols1 = Controls(num_qbits)
trols1.bit_pos_to_kind = TF_dict.copy()
trols1.refresh_lists()
trols2 = Controls(num_qbits)
def write_cnots(diff_bvec1, init_prev_T_bit):
prev_T_bit = init_prev_T_bit
while True:
cur_T_bit = diff_bvec1.find_T_bit_to_left_of(prev_T_bit)
if cur_T_bit == -1:
break
trols2.bit_pos_to_kind = TF_dict.copy()
trols2.bit_pos_to_kind[cur_T_bit + ntf] = True
trols2.refresh_lists()
self.write_controlled_one_qbit_gate(
ntf + init_prev_T_bit, trols2, OneQubitGate.sigx)
prev_T_bit = cur_T_bit
norma = np.power(np.sqrt(2), num_MP_trols)
# for first A factor, f = 0, just global phase
# write conditioned global phase
global_ph = conj_rads[0]*norma/len(conj_rads)
if abs(global_ph) > 1e-6:
self.write_controlled_one_qbit_gate(ntf, trols1,
OneQubitGate.phase_fac, [global_ph])
while f < num_factors:
cur_bvec.dec_rep = lazy
# Since we have excluded f=0, f always has at least one T bit.
cur_rot_bpos = cur_bvec.find_rightmost_T_bit()
# print(cur_bvec.get_bit_string(), cur_rot_bpos)
rads = conj_rads[cur_bvec.dec_rep]/norma
if abs(rads) < 1e-6:
pass
else:
# If cur_rot_bpos equals (doesn't equal) prev_rot_bpos,
# then there is (isn't) cancellation between:
# (1)the c-nots sigma_x(cur_rot_bpos)^n()
# contributed by the right part of the current A factor
# and
# (2)the c-nots sigma_x(prev_rot_bpos)^n()
# contributed by the left part of the previous A factor.
if cur_rot_bpos == prev_rot_bpos:
diff_bvec = BitVector.new_with_T_on_diff(
cur_bvec, prev_bvec)
write_cnots(diff_bvec, cur_rot_bpos)
else:
write_cnots(prev_bvec, prev_rot_bpos)
write_cnots(cur_bvec, cur_rot_bpos)
diff_bvec = BitVector.copy(cur_bvec)
self.write_controlled_one_qbit_gate(
ntf + cur_rot_bpos, trols1, OneQubitGate.rot_ax, [rads, 3])
prev_bvec = BitVector.copy(cur_bvec)
prev_rot_bpos = cur_rot_bpos
f, lazy = BitVector.lazy_advance(f, lazy)
# Don't forget the leftmost c-nots
write_cnots(prev_bvec, prev_rot_bpos)
[docs] def write(self):
"""
Main write function of this class. All other write functions are
internal. This function writes a d-unitary in the style specified by
the parameter self.style.
Returns
-------
None
"""
if all([abs(ang) < 1e-6 for ang in self.rad_angles]):
print("unit d-unitary detected")
return None
if self.style == 'one_line':
self.write_one_line()
elif self.style == 'exact':
self.write_exact()
else:
assert False, "unsupported d-unitary expansion style"
[docs] @staticmethod
def du_mat(rad_angles, herm_conj=False):
"""
This function returns a square numpy array whose diagonal is the
component-wise exp(1j* ) of rad_angles.
Parameters
----------
rad_angles : list(float)
herm_conj : bool
If True, uses exp(-1j*rad_angles).
If False, uses exp(+1j*rad_angles)
Returns
-------
np.ndarray
"""
sign = 1
if herm_conj:
sign = -1
return np.diag(np.exp(1j*sign*np.array(rad_angles)))
if __name__ == "__main__":
def main():
nt = 1
nf = 2
num_MP_trols = 3
num_angles = (1 << num_MP_trols)
rad_angles = list(np.random.rand(num_angles)*2*np.pi)
for style in ['one_line', 'exact']:
num_gbits = 0
if style == 'oracular':
num_gbits = 3
num_qbits = nt + nf + num_MP_trols + num_gbits
emb = CktEmbedder(num_qbits, num_qbits)
file_prefix = "d_unitary_test_" + style
wr = DiagUnitarySEO_writer(file_prefix, emb, style, rad_angles,
num_T_trols=nt, num_F_trols=nf, num_gbits=num_gbits)
wr.write()
wr.close_files()
file_prefix = "d_unitary_exact_check"
num_qbits = 4
num_angles = (1 << num_qbits)
emb = CktEmbedder(num_qbits, num_qbits)
rad_angles = list(np.random.rand(num_angles)*2*np.pi)
# av = sum(rad_angles)/len(rad_angles)
# rad_angles = list(np.array(rad_angles)-av)
wr = DiagUnitarySEO_writer(file_prefix, emb, 'exact', rad_angles)
wr.write()
wr.close_files()
matpro = SEO_MatrixProduct(file_prefix, num_qbits)
exact_mat = DiagUnitarySEO_writer.du_mat(rad_angles)
print("error=", np.linalg.norm(matpro.prod_arr - exact_mat))
# print(matpro.prod_arr)
# print(np.diag(exact_mat))
main()