```from qubiter.SEO_writer import *

[docs]class StairsDerivCkt_writer(SEO_writer):
"""
This class is a subclass of `SEO_writer`. It writes several intermediary
stairs derivative circuits that will be used in class
`StairsDeriv_native` for calculating the gradients of a quantum cost
function (mean hamiltonian).

Suppose U = exp[i*(t_0 + t_1*sigx + t_2*sigy + t_3*sigz)], where sigx,
sigy, sigz are the Pauli matrices and t_r for r in range(4) are 4 real
parameters. To take the derivative wrt t_r of a given multi-controlled
gate U in a stairs circuit, we need to evaluate several circuits (we
call them dparts, which stands for derivative parts). Say, for instance,
that ``GATE= @---O---+---U``. To calculate d/dt_r GATE(t_0, t_1, t_2, t_3),
for r=0,1, 2, 3, we need to calculate a new circuit wherein the GATE in
the parent circuit is replaced by::

sum_k  c_k  @---@---O---+---U_k

(which is said to have `has_neg_polarity`=False) and::

sum_k  c_k  @---@---O---+---U_k
Z---@---O   |   |

(which is said to have `has_neg_polarity`=True)

Also, some extra stuff (a coda) must be appended to the end of the
parent stairs circuit.

Note that an extra "ancilla" qbit has been added (as the new last qubit)
to the parent stairs circuit being differentiated. So if the parent
stairs circuit has a number `parent_num_qbits` of qubits, then the one
written by this class has that many qubits plus one.

The index r which is in range(4) is called the derivative direction (
`deriv_direc`)

`gate_str_to_rads_list` is the same as for the parent stairs circuit.

`deriv_gate_str` is a well formed gate_str that specifies which U is
being differentiated

The index k is given as a string called `dpart_name` ("dpart" stands for
derivative part).

The coefficients c_k can be obtained via the method get_coef_of_dpart()

Each U_k is a U(2) matrix itself, and its 4 parameters are defined in
terms of the parameters tlist=[t_0, t_1, t_2, t_3] of the U(tlist) being
differentiated, via 4 functions of tlist. These functions can be
obtained via the method get_fun_name_to_fun().

Attributes
----------
deriv_direc : int
in range(4)
deriv_gate_str : str
dpart_name : str
has_neg_polarity : bool

"""

[docs]    def __init__(self, deriv_gate_str, has_neg_polarity,
deriv_direc, dpart_name,
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
----------
deriv_gate_str : str
has_neg_polarity : bool
deriv_direc : int
in range(4)
dpart_name : str
file_prefix : str
emb : CktEmbedder
kwargs : dict
key-word arguments of SEO_writer

Returns
-------

"""
SEO_writer.__init__(self, file_prefix, emb, **kwargs)
self.deriv_gate_str = deriv_gate_str
self.has_neg_polarity = has_neg_polarity
self.deriv_direc = deriv_direc
self.dpart_name = dpart_name

assert deriv_direc in range(4)
if deriv_gate_str == 'prior':
assert has_neg_polarity is None

self.write()

[docs]    def write(self):
"""
This method writes English and Picture files for a quantum circuit
Der. Der is used in calculating the derivative of a parent stairs
circuit Pa with respect to one of 4 parameters for each of the
multi-controlled U's of Pa.

Returns
-------

"""
num_qbits = self.emb.num_qbits_bef
anc_bit_pos = num_qbits-1

u2_pos = self.get_u2_pos(gate_str)
trols = StairsCkt_writer.get_controls_from_gate_str(
num_qbits, gate_str)
if gate_str == self.deriv_gate_str:
# add control on ancilla qubit
trols.bit_pos_to_kind[anc_bit_pos] = True
trols.refresh_lists()
self.deriv_direc,
self.dpart_name)
else:
self.write_H(anc_bit_pos)
else:
self.write_controlled_one_qbit_gate(u2_pos, trols,
if gate_str == self.deriv_gate_str and gate_str != 'prior' and \
self.has_neg_polarity:
# remove ancilla qubit control that was added previously
del trols.bit_pos_to_kind[anc_bit_pos]
trols.refresh_lists()
# add controlled sigz for neg X term
self.write_controlled_one_qbit_gate(anc_bit_pos, trols,
OneQubitGate.sigz)

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

Class StairsCkt_writer has a method of same name but which returns a
different value for the same gate_str input. This is due to the fact
that the circuit generated by this class has an extra ancilla qubit
compared with its parent circuit.

Parameters
----------
gate_str : str

Returns
-------
int

"""
# last qubit position, num_qbits-1, is reserved for ancilla qubit
# "prior" gate_str has U2 at bit pos num_qbits-2
num_qbits = self.emb.num_qbits_bef
if gate_str == 'prior':
return num_qbits - 2
else:
return num_qbits - 2 - len(gate_str) // 2

[docs]    @staticmethod
"""
Internal method that checks whether t_list is of form list[str] or
list[float]. In first case, it uses var_num_to_rads to replace
t_list by a list[float].

Parameters
----------
t_list : list[float|str]

Returns
-------
list[float]

"""

if any([isinstance(t, str) for t in t_list]):
't_list has #int strings, items not all floats,' \
' so must provide a var_num_to_rads'
t_list = [var_num_to_rads[int(t[1:])] for t in t_list]
return t_list

[docs]    @staticmethod
"""
This method returns the rads list (list of 4 floats) for a given
given: 1. the rads list `t_list` of the U that is being
differentiated, 2. the dpart (derivative part), and 3. which of 4
directions deriv_direc the derivative is wrt.

var_num_to_rads is used if self wrote the English file with #int
string symbols for placeholder variables. var_num_to_rads is used to
float those strings.

Parameters
----------
t_list : list[float | str]
deriv_direc : int
int in range(4)
dpart_name : str
var_num_to_rads : dict[int, float] | None

Returns
-------
list[float]

"""
t_list = StairsDerivCkt_writer.\
t_vec = t_list[1:]
t = np.linalg.norm(np.array(t_vec))

if deriv_direc == 0:
assert dpart_name == 'single'
else:  # deriv_direc in [1, 2, 3]
if dpart_name == '1':
if t > 1e-6:
for k in range(1, 4):
elif dpart_name == 's':
elif dpart_name == '1s':
if t > 1e-6:
for k in range(1, 4):
else:
assert False, 'unsupported deriv part name'

[docs]    @staticmethod
def get_coef_of_dpart(t_list,
deriv_direc,
dpart_name,
"""
This method returns coefficient of dpart (derivative part), either
p1, ps or -p1*ps

var_num_to_rads is used if self wrote the English file with #int
string symbols for placeholder variables. var_num_to_rads is used to
float those strings.

Parameters
----------
t_list : list[float | str]
deriv_direc : int
int in range(4)
dpart_name : str
var_num_to_rads : dict[int, float] | None

Returns
-------
float

"""
# t stands for theta not time
t_list = StairsDerivCkt_writer.\

# space components
t_vec = t_list[1:]
t = np.linalg.norm(np.array(t_vec))
if t < 1e-6:
ps = 1.
p1 = 0.
else:
ps = np.sin(t)/t
p1 = t_list[deriv_direc]/t

if deriv_direc == 0:
assert dpart_name == 'single'
coef = 1.
else:  # deriv_direc in [1, 2, 3]
if dpart_name == '1':
coef = p1
elif dpart_name == 's':
coef = ps
elif dpart_name == '1s':
coef = -p1*ps
else:
assert False, 'unsupported deriv part name'
return coef

[docs]    @staticmethod
def get_fun_name_to_fun(t_list, deriv_direc, dpart_name):
"""
This method returns a dictionary fun_name_to_fun mapping the
function name to function, for all functions defined by this class.

Parameters
----------
t_list : list[float | str]
deriv_direc : int
dpart_name : str

Returns
-------
dict[str, function]

"""
fun_name_to_fun = {}
if isinstance(t_list, str):
for k in range(4):
fun_name_to_fun[fun_name] =\
(lambda t0, t1, t2, t3, k1=k,
x2=deriv_direc, x3=dpart_name:
StairsDerivCkt_writer.
get_rads_list_of_dpart([t0, t1, t2, t3], x2, x3)[k1])
return fun_name_to_fun

if __name__ == "__main__":
def main():
num_qbits = 4
parent_num_qbits = num_qbits - 1  # one bit for ancilla

file_prefix = 'stairs_deriv_writer_test'
emb = CktEmbedder(num_qbits, num_qbits)
for deriv_direc, dpart_name, has_neg_polarity in \
[(0, 'single', None), (3, 's', True)]:
wr = StairsDerivCkt_writer(deriv_gate_str,
has_neg_polarity,
deriv_direc,
dpart_name,