from qubiter.SEO_writer import *
import itertools as it
import pprint as pp
import collections as col
[docs]class StairsCkt_writer(SEO_writer):
"""
This class is a subclass of class SEO_writer and it writes a "Stairs
Circuit". For example, this is what the Picture file of a Stairs Circuit
looks like for num_qbits = 3::
U | |
O---U |
@---U |
O---O---U
O---@---U
@---O---U
@---@---U
Here, U is a general U(2) matrix with 4 parameters, all of which can be
made into placeholder variables. If each U is represented by a node and
the controls of each U represent its parents, then this quantum circuit
can be represented by a fully connected Quantum Bayesian Network (QB
net). (See my >10 year old blog called "Quantum Bayesian Networks" for
more info than you would ever want to know about QB nets).
This class can also be asked to construct a QB net that is **not** fully
connected, by limiting the number of controls for a given U to fewer
than all the ones to its left. For example, suppose that in the
num_qbits=3 case, we restrict the parents of the U in the last step to
just one, instead of the 2 parents that it has in the fully connected
case. Then we get::
U | |
O---U |
@---U |
O---+---U
@---+---U
or::
U | |
O---U |
@---U |
| O---U
| @---U
The constructor of this class has as input an ordered dictionary called
gate_str_to_rads_list. This dictionary gives for each gate in the
quantum circuit, a gate string gate_str that specifies the gate.
gate_str_to_rads_list maps gate_str to a list of 4 floats (or
placeholder variables for those floats) for the 4 parameters of the U
matrix. For example, here are possible values for gate_str_to_rads_list
for the num_qbits=3 fully connected qb net
with every rads_list item filled with the same constant .3::
{'prior': [0.3, 0.3, 0.3, 0.3],
'2F': [0.3, 0.3, 0.3, 0.3],
'2T': [0.3, 0.3, 0.3, 0.3],
'2F1F': [0.3, 0.3, 0.3, 0.3],
'2F1T': [0.3, 0.3, 0.3, 0.3],
'2T1F': [0.3, 0.3, 0.3, 0.3],
'2T1T': [0.3, 0.3, 0.3, 0.3]}
with every rads_list item filled by a random number between 0 and 2pi::
{'prior': [0.46731839721496604,
0.012285135138256131,
0.20001353832948487,
0.36694428209569985],
'2F': [4.1968011007222898,
5.1978252498063808,
4.8063090848060321,
4.2509081392354409],
'2T': [4.3359074640905213,
2.0749617893052315,
4.555666727197961,
5.3092010293653802],
'2F1F': [0.99177045463186475,
3.3344615340103325,
2.1441702948866386,
2.4603764283165521],
'2F1T': [4.0909522483111145,
2.0714182784661888,
5.4034187072431923,
6.0856723571386766],
'2T1F': [4.0000452017061194,
3.7193341571216658,
3.381322125034953,
5.4492142181489802],
'2T1T': [6.2597553541046853,
0.077807529496169509,
3.7389318319862217,
6.2233264819972307]}
with every rads_list item filled by a unique placeholder variable string::
{'prior': ['#50', '#51', '#52', '#53'],
'2F': ['#500', '#501', '#502', '#503'],
'2T': ['#510', '#511', '#512', '#513'],
'2F1F': ['#5000', '#5001', '#5002', '#5003'],
'2F1T': ['#5010', '#5011', '#5012', '#5013'],
'2T1F': ['#5100', '#5101', '#5102', '#5103'],
'2T1T': ['#5110', '#5111', '#5112', '#5113']}
This is what gate_str_to_rads_list looks like in the num_qbits=3 case,
when the last U has only one parent (qbit 2) instead of two parents (
qbits 1 and 2)::
{'prior': ['#50', '#51', '#52', '#53'],
'2F': ['#500', '#501', '#502', '#503'],
'2T': ['#510', '#511', '#512', '#513'],
'2F1_': ['#5050', '#5051', '#5052', '#5053'],
'2T1_': ['#5150', '#5151', '#5152', '#5153']}
Note that all placeholder strings begin with '#5' to insure that once
the hash character is removed, the remaining number doesn't start with
'0'. Note that characters '_' and '5' represent bits whose values are
unspecified.
Attributes
----------
gate_str_to_rads_list : OrderedDict[str, list[float|str]]
"""
[docs] def __init__(self, gate_str_to_rads_list, file_prefix, emb, **kwargs):
"""
Constructor
This constructor writes English and Picture files but it doesn't
close those files after writing them. You must do that yourself
using close_files().
Parameters
----------
gate_str_to_rads_list : dict[str, list[float|str]]
file_prefix : str
file prefix for English and Picture files written by this class
emb : CktEmbedder
kwargs : dict
key-word arguments of SEO_writer
Returns
-------
"""
SEO_writer.__init__(self, file_prefix, emb, **kwargs)
self.gate_str_to_rads_list = gate_str_to_rads_list
self.write()
[docs] @staticmethod
def get_gate_str_to_rads_list(num_qbits, fill_type, rads_const=None,
u2_bit_to_higher_bits=None):
"""
This method returns a gate_str_to_rads_list constructed according to
the specs given by its arguments.
fill_type is a string in ['const', 'rand', '#int'] The 3 types of
fill_type have already been illustrated in the class docstring. If
the fill_type is 'const', then the method expects a float for
rads_const.
u2_bit_to_higher_bits is used to restrict the controls of each U.
For example, for num_qbits=3,
u2_bit_to_higher_bits = {0: [1, 2], 1: [2], 2: []}
specifies a fully connected qb net, whereas
u2_bit_to_higher_bits = {0: [2], 1: [2], 2: []}
means qubit 0 has qubit 2 but not 1 as parent.
Parameters
----------
num_qbits : int
fill_type : str
either 'const', 'rand' or '#int'
rads_const : float | None
u2_bit_to_higher_bits : dict[int, list[int]]
Returns
-------
OrderedDict[str, list[float]]
"""
# each "step" may have several gate_str of same length
const_list = [rads_const for k in range(4)]
gate_str_to_rads_list = col.OrderedDict()
if fill_type == 'const':
assert rads_const is not None
gate_str_to_rads_list['prior'] = const_list
elif fill_type == 'rand':
rand_list = list(np.random.random_sample((4,)))
gate_str_to_rads_list['prior'] = rand_list
elif fill_type == '#int':
# all placeholder variables will start
# with 5 to avoid starting with 0
gate_str_to_rads_list['prior'] = ['#50', '#51', '#52', '#53']
else:
assert False, 'unsupported fill type'
pair = ['F', 'T']
singlet = ['_']
for tup_len in range(1, num_qbits):
u2_pos = num_qbits - tup_len - 1
pa_range = range(u2_pos+1, num_qbits)
parent_to_list = {k: pair for k in pa_range}
if u2_bit_to_higher_bits:
parent_to_list = {k: singlet for k in pa_range}
for pa_bit in u2_bit_to_higher_bits[u2_pos]:
assert u2_pos < pa_bit < num_qbits
parent_to_list[pa_bit] = pair
list_of_lists = [parent_to_list[k] for k in reversed(pa_range)]
# print("mmmnnnnnn", list_of_lists)
for tuple_of_FTs in it.product(*list_of_lists):
s = ''
for k in range(tup_len):
s += str(num_qbits - 1 - k) + tuple_of_FTs[k]
if fill_type == 'const':
gate_str_to_rads_list[s] = const_list
elif fill_type == 'rand':
rand_list = list(2*np.pi*np.random.random_sample((4,)))
gate_str_to_rads_list[s] = rand_list
elif fill_type == '#int':
hash_str = '#5'
for k in range(tup_len):
x = tuple_of_FTs[k]
if x == '_':
hash_str += '5'
elif x == 'F':
hash_str += '0'
elif x == 'T':
hash_str += '1'
gate_str_to_rads_list[s] =\
[hash_str + str(k) for k in range(4)]
else:
assert False
return gate_str_to_rads_list
[docs] @staticmethod
def get_all_var_nums(gate_str_to_rads_list):
"""
This method scans each rads_list of gate_str_to_rads_list for items
of the type '#x' where x is an int. Every int x is added to a list
all_var_nums which is returned.
Parameters
----------
gate_str_to_rads_list : OrderedDict[str, list[float|str]]
Returns
-------
list[int]
"""
all_var_nums = []
for rads_list in gate_str_to_rads_list.values():
for rads in rads_list:
if isinstance(rads, str) and rads[0] == '#':
all_var_nums.append(int(rads[1:]))
return all_var_nums
[docs] @staticmethod
def get_var_num_to_rads(gate_str_to_rads_list, fill_type,
rads_const=None):
"""
This method returns a dict var_num_to_rads obtained as follows. The
rads lists in gate_str_to_rads_list are scanned for items of the
type '#x' where x is an int. Then x is mapped to a float, either the
constant rads_const if fill_type is 'const', or a random number if
fill type is 'rand'.
Parameters
----------
gate_str_to_rads_list : OrderedDict[str, list[float|str]]
fill_type : str
rads_const : float | None
Returns
-------
dict[int, float]
"""
var_num_to_rads = {}
for rads_list in gate_str_to_rads_list.values():
for rads in rads_list:
if isinstance(rads, str) and rads[0] == '#':
if fill_type == 'const':
assert rads_const is not None
var_num_to_rads[int(rads[1:])] = rads_const
elif fill_type == 'rand':
var_num_to_rads[int(rads[1:])] =\
2*np.pi*np.random.random()
else:
assert False, 'unsupported fill type'
return var_num_to_rads
[docs] @staticmethod
def make_array_from_gate_str_to_rads_list(gate_str_to_rads_list):
"""
This method returns a numpy array which is constructed from
gate_str_to_rads_list by vertically stacking (with np.vstack()) its
rads_lists
Parameters
----------
gate_str_to_rads_list : dict[str, list[float|str]]
Returns
-------
np.ndarray
"""
return np.vstack([np.array(rads_list) for rads_list in
gate_str_to_rads_list.values()])
[docs] def get_u2_pos(self, gate_str):
"""
Given a well formed gate_str (one of the keys of
gate_str_to_rads_list), this method returns the bit position of the
U(2) matrix.
Parameters
----------
gate_str : str
Returns
-------
int
"""
num_qbits = self.emb.num_qbits_bef
if gate_str != 'prior':
u2_pos = num_qbits - len(gate_str) // 2 - 1
else:
u2_pos = num_qbits-1
return u2_pos
[docs] @staticmethod
def get_controls_from_gate_str(num_qbits, gate_str):
"""
This method returns an object of class Controls, constructed from
info in the input `gate_str` (a well formed key of
gate_str_to_rads_lis)
Parameters
----------
num_qbits : int
gate_str : str
Returns
-------
Controls
"""
trols = Controls(num_qbits)
if gate_str != 'prior':
for k in range(len(gate_str)//2):
trol_pos = int(gate_str[2 * k])
trol_kind = gate_str[2 * k + 1]
# allow for possibility that trol_kind = '_' (no trol)
if trol_kind == 'T':
trols.bit_pos_to_kind[trol_pos] = True
elif trol_kind == 'F':
trols.bit_pos_to_kind[trol_pos] = False
trols.refresh_lists()
return trols
[docs] def write(self):
"""
This method writes English and Picture files for a Stairs Circuit.
Returns
-------
"""
num_qbits = self.emb.num_qbits_bef
for gate_str, rads_list in self.gate_str_to_rads_list.items():
num_qbits = self.emb.num_qbits_bef
trols = StairsCkt_writer.get_controls_from_gate_str(
num_qbits, gate_str)
u2_pos = self.get_u2_pos(gate_str)
self.write_controlled_one_qbit_gate(u2_pos, trols,
OneQubitGate.u2, rads_list)
if __name__ == "__main__":
def main():
num_qbits = 3
for fill_type in ['const', 'rand', '#int']:
di = StairsCkt_writer.get_gate_str_to_rads_list(
num_qbits, fill_type, rads_const=.3)
pp.pprint(di)
u2_bit_to_higher_bits = {0: [2], 1: [2], 2: []}
di = StairsCkt_writer.get_gate_str_to_rads_list(
num_qbits, "#int", u2_bit_to_higher_bits=u2_bit_to_higher_bits)
pp.pprint(di)
vn_to_r = StairsCkt_writer.get_var_num_to_rads(di,
fill_type='const',
rads_const=.3)
pp.pprint(vn_to_r)
arr = StairsCkt_writer.make_array_from_gate_str_to_rads_list(di)
print("arr=\n", arr)
num_qbits = 4
gate_str_to_rads_list = StairsCkt_writer.get_gate_str_to_rads_list(
num_qbits, '#int')
file_prefix = 'stairs_writer_test'
emb = CktEmbedder(num_qbits, num_qbits)
wr = StairsCkt_writer(gate_str_to_rads_list, file_prefix, emb)
wr.close_files()
wr.print_eng_file()
wr.print_pic_file()
main()