|
2 | 2 | import typing
|
3 | 3 | import warnings
|
4 | 4 | from dataclasses import dataclass
|
5 |
| - |
| 5 | +from copy import deepcopy |
| 6 | +from numbers import Real |
6 | 7 | import numpy
|
7 | 8 |
|
8 |
| -from tequila import BitString, QCircuit, TequilaException |
| 9 | +from tequila import BitString, QCircuit, TequilaException,Variable,compile_circuit |
9 | 10 | from tequila.circuit import gates
|
10 |
| - |
11 | 11 | try:
|
12 | 12 | from openfermion.ops.representations import get_active_space_integrals # needs openfermion 1.3
|
13 | 13 | except ImportError as E:
|
@@ -50,16 +50,132 @@ def __init__(self, generator, p0, transformation, indices=None, *args, **kwargs)
|
50 | 50 | self._name = "FermionicExcitation"
|
51 | 51 | self.transformation = transformation
|
52 | 52 | self.indices = indices
|
53 |
| - |
| 53 | + if not hasattr(indices[0],"__len__"): |
| 54 | + self.indices = [(indices[2 * i], indices[2 * i+1]) for i in range(len(indices) // 2)] |
| 55 | + self.sign = self.format_excitation_variables(self.indices) |
| 56 | + self.indices = self.format_excitation_indices(self.indices) |
54 | 57 | def compile(self, *args, **kwargs):
|
55 | 58 | if self.is_convertable_to_qubit_excitation():
|
56 | 59 | target = []
|
57 | 60 | for x in self.indices:
|
58 | 61 | for y in x:
|
59 | 62 | target.append(y)
|
60 |
| - return gates.QubitExcitation(target=target, angle=-self.parameter, control=self.control) |
| 63 | + return gates.QubitExcitation(target=target, angle=self.parameter, control=self.control) |
| 64 | + else: |
| 65 | + if self.transformation.lower().strip("_") == "jordanwigner": |
| 66 | + return self.fermionic_excitation(angle=self.sign*self.parameter, indices=self.indices, control=self.control,opt=False) |
| 67 | + else: |
| 68 | + return gates.Trotterized(generator=self.generator, control=self.control, angle=self.parameter, steps=1) |
| 69 | + def format_excitation_indices(self, idx): |
| 70 | + """ |
| 71 | + Consistent formatting of excitation indices |
| 72 | + idx = [(p0,q0),(p1,q1),...,(pn,qn)] |
| 73 | + sorted as: p0<p1<pn and pi<qi |
| 74 | + :param idx: list of index tuples describing a single(!) fermionic excitation |
| 75 | + :return: list of index tuples |
| 76 | + """ |
| 77 | + |
| 78 | + idx = [tuple(sorted(x)) for x in idx] |
| 79 | + idx = sorted(idx, key=lambda x: x[0]) |
| 80 | + return list(idx) |
| 81 | + def format_excitation_variables(self, idx): |
| 82 | + """ |
| 83 | + Consistent formatting of excitation variable |
| 84 | + idx = [(p0,q0),(p1,q1),...,(pn,qn)] |
| 85 | + sorted as: pi<qi and p0 < p1 < p2 |
| 86 | + :param idx: list of index tuples describing a single(!) fermionic excitation |
| 87 | + :return: sign of the variable with re-ordered indices |
| 88 | + """ |
| 89 | + sig = 1 |
| 90 | + for pair in idx: |
| 91 | + if pair[1]>pair[0]: |
| 92 | + sig *= -1 |
| 93 | + for pair in range(len(idx)-1): |
| 94 | + if idx[pair+1][0]>idx[pair][0]: |
| 95 | + sig *= -1 |
| 96 | + return sig |
| 97 | + def cCRy(self, target: int, dcontrol: typing.Union[list, int], control: typing.Union[list, int], |
| 98 | + angle: typing.Union[Real, Variable, typing.Hashable], case: int = 1) -> QCircuit: |
| 99 | + ''' |
| 100 | + Compilation of CRy as on https://doi.org/10.1103/PhysRevA.102.062612 |
| 101 | + If not control passed, Ry returned |
| 102 | + Parameters |
| 103 | + ---------- |
| 104 | + case: if 1 employs eq. 12 from the paper, if 0 eq. 13 |
| 105 | + ''' |
| 106 | + if control is not None and not len(control): |
| 107 | + control = None |
| 108 | + if isinstance(dcontrol, int): |
| 109 | + dcontrol = [dcontrol] |
| 110 | + if not len(dcontrol): |
| 111 | + return compile_circuit(gates.Ry(angle=angle, target=target, control=control)) |
61 | 112 | else:
|
62 |
| - return gates.Trotterized(generator=self.generator, control=self.control, angle=self.parameter, steps=1) |
| 113 | + if isinstance(angle, str): |
| 114 | + angle = Variable(angle) |
| 115 | + U = QCircuit() |
| 116 | + aux = dcontrol[0] |
| 117 | + ctr = deepcopy(dcontrol) |
| 118 | + ctr.pop(0) |
| 119 | + if case: |
| 120 | + U += self.cCRy(target=target, dcontrol=ctr, angle=angle / 2, case=1, control=control) + gates.H( |
| 121 | + aux) + gates.CNOT(target, aux) |
| 122 | + U += self.cCRy(target=target, dcontrol=ctr, angle=-angle / 2, case=0, control=control) + gates.CNOT( |
| 123 | + target, aux) + gates.H(aux) |
| 124 | + else: |
| 125 | + U += gates.H(aux) + gates.CNOT(target, aux) + self.cCRy(target=target, dcontrol=ctr, angle=-angle / 2, |
| 126 | + case=0, control=control) |
| 127 | + U += gates.CNOT(target, aux) + gates.H(aux) + self.cCRy(target=target, dcontrol=ctr, angle=angle / 2, |
| 128 | + case=1, control=control) |
| 129 | + return U |
| 130 | + |
| 131 | + def fermionic_excitation(self, angle: typing.Union[Real, Variable, typing.Hashable], indices: typing.List, |
| 132 | + control: typing.Union[int, typing.List] = None, opt: bool = True) -> QCircuit: |
| 133 | + ''' |
| 134 | + Excitation [(i,j),(k,l)],... compiled following https://doi.org/10.1103/PhysRevA.102.062612 |
| 135 | + opt: whether to optimized CNOT H CNOT --> Rz Rz CNOT Rz |
| 136 | + ''' |
| 137 | + lto = [] |
| 138 | + lfrom = [] |
| 139 | + if isinstance(indices,tuple) and not hasattr(indices[0],"__len__"): |
| 140 | + indices = [(indices[2 * i], indices[2 * i + 1]) for i in range(len(indices) // 2)] |
| 141 | + for pair in indices: |
| 142 | + lfrom.append(pair[0]) |
| 143 | + lto.append(pair[1]) |
| 144 | + Upair = QCircuit() |
| 145 | + if isinstance(angle, str) or isinstance(angle, tuple): |
| 146 | + angle = Variable(angle) |
| 147 | + for i in range(len(lfrom) - 1): |
| 148 | + Upair += gates.CNOT(lfrom[i + 1], lfrom[i]) |
| 149 | + Upair += gates.CNOT(lto[i + 1], lto[i]) |
| 150 | + Upair += gates.X(lto[i]) + gates.X(lfrom[i]) |
| 151 | + Upair += gates.CNOT(lto[-1], lfrom[-1]) |
| 152 | + crt = lfrom[::-1] + lto |
| 153 | + Uladder = QCircuit() |
| 154 | + pairs = lfrom + lto |
| 155 | + pairs.sort() |
| 156 | + orbs = [] |
| 157 | + for o in range(len(pairs) // 2): |
| 158 | + orbs += [*range(pairs[2 * o] + 1, pairs[2 * o + 1])] |
| 159 | + if len(orbs): |
| 160 | + for o in range(len(orbs) - 1): |
| 161 | + Uladder += gates.CNOT(orbs[o], orbs[o + 1]) |
| 162 | + Uladder += compile_circuit(gates.CZ(orbs[-1], lto[-1])) |
| 163 | + crt.pop(-1) |
| 164 | + if control is not None and (isinstance(control, int) or len(control) == 1): |
| 165 | + if isinstance(control, int): |
| 166 | + crt.append(control) |
| 167 | + else: |
| 168 | + crt = crt + control |
| 169 | + control = [] |
| 170 | + Ur = self.cCRy(target=lto[-1], dcontrol=crt, angle=angle, control=control) |
| 171 | + Upair2 = Upair.dagger() |
| 172 | + if opt: |
| 173 | + Ur.gates.pop(-1) |
| 174 | + Ur.gates.pop(-1) |
| 175 | + Upair2.gates.pop(0) |
| 176 | + Ur += gates.Rz(numpy.pi / 2, target=lto[-1]) + gates.Rz(-numpy.pi / 2, target=lfrom[-1]) |
| 177 | + Ur += gates.CNOT(lto[-1], lfrom[-1]) + gates.Rz(numpy.pi / 2, target=lfrom[-1]) + gates.H(lfrom[-1]) |
| 178 | + return Upair + Uladder + Ur + Uladder.dagger() + Upair2 |
63 | 179 |
|
64 | 180 | def __str(self):
|
65 | 181 | if self.indices is not None:
|
|
0 commit comments