Skip to content

Commit c7c00d8

Browse files
authored
Merge pull request #312 from tequilahub/devel
Update Master to v.1.9.0
2 parents 7c6f5b2 + 380e1a1 commit c7c00d8

22 files changed

+843
-152
lines changed

Diff for: .github/workflows/ci_backends.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
# myqlm does not work in python3.7
3333
pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1"
3434
elif [ $ver -eq 8 ]; then
35-
pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1" "myqlm"
35+
pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1" "myqlm" "cirq-google"
3636
fi
3737
pip install -e .
3838
- name: Lint with flake8

Diff for: .github/workflows/ci_chemistry_pyscf.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ jobs:
4242
python -m pip install 'h5py <= 3.1'
4343
cd tests
4444
ls
45-
pytest test_chemistry.py -m "not dependencies"
45+
pytest test_chemistry.py test_TrotErr.py -m "not dependencies"
4646
pytest test_adapt.py -m "not dependencies"
4747
cd ../

Diff for: .github/workflows/ci_pyquil.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ jobs:
2929
python -m pip install --upgrade pip
3030
pip install --upgrade pytest
3131
pip install -r requirements.txt
32-
pip install "pyquil<3.0"
32+
pip install "pyquil<3.0" # needs updates
3333
pip install -e .
34+
pip install --upgrade pip 'urllib3<2' # issues with old pyquil version otherwise
3435
docker pull rigetti/qvm:edge
3536
docker pull rigetti/quilc
3637
docker run --rm -itd -p 5555:5555 rigetti/quilc -R

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Tequila can execute the underlying quantum expectation values on state of the ar
1515

1616
# Installation
1717
Recommended Python version is 3.8-3.9.
18-
Tequila supports linux, osx and windows. However, not all optional dependencies are supported on windows.
18+
Tequila supports linux, osx and windows. However, not all optional dependencies are supported on windows.
1919

2020
## Install from PyPi
2121
**Do not** install like this: (Minecraft lovers excluded)

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ qulacs # default simulator (best integration), remove if the installation gives
1313

1414
#optional quantum backends
1515
#cirq >= 0.9.2 #
16+
#cirq_google
1617
#qiskit>=0.30
1718
#pyquil<3.0 # you also need to install the forest-sdk
1819
#qulacs-gpu # you can't have qulacs and qulacs-gpu at the same time

Diff for: src/tequila/circuit/_gates_impl.py

+42-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from tequila.objective.objective import Variable, FixedVariable, assign_variable
77
from tequila.hamiltonian import PauliString, QubitHamiltonian, paulis
88
from tequila.tools import list_assignment
9-
from numpy import pi, sqrt
9+
import numpy as np
1010

1111
from dataclasses import dataclass
1212

@@ -234,12 +234,12 @@ def shifted_gates(self, r=None):
234234
if r is None:
235235
r = self.eigenvalues_magnitude
236236

237-
s = pi / (4 * r)
237+
s = np.pi / (4 * r)
238238
if self.is_controlled() and not self.assume_real:
239239
# following https://arxiv.org/abs/2104.05695
240240
shifts = [s, -s, 3 * s, -3 * s]
241-
coeff1 = (sqrt(2) + 1)/sqrt(8) * r
242-
coeff2 = (sqrt(2) - 1)/sqrt(8) * r
241+
coeff1 = (np.sqrt(2) + 1)/np.sqrt(8) * r
242+
coeff2 = (np.sqrt(2) - 1)/np.sqrt(8) * r
243243
coefficients = [coeff1, -coeff1, -coeff2, coeff2]
244244
circuits = []
245245
for i, shift in enumerate(shifts):
@@ -344,7 +344,7 @@ class PowerGateImpl(ParametrizedGateImpl):
344344

345345
@property
346346
def power(self):
347-
return self.parameter/pi
347+
return self.parameter/np.pi
348348

349349
def __init__(self, name, generator: QubitHamiltonian, target: list, power, control: list = None):
350350
if generator is None:
@@ -353,7 +353,7 @@ def __init__(self, name, generator: QubitHamiltonian, target: list, power, cont
353353
if name is None:
354354
assert generator is not None
355355
name = str(generator)
356-
super().__init__(name=name, parameter=power * pi, target=target, control=control, generator=generator)
356+
super().__init__(name=name, parameter=power * np.pi, target=target, control=control, generator=generator)
357357

358358

359359
class GeneralizedRotationImpl(DifferentiableGateImpl):
@@ -376,11 +376,44 @@ def extract_targets(generator):
376376
targets += [k for k in ps.keys()]
377377
return tuple(set(targets))
378378

379-
def __init__(self, angle, generator, control=None, eigenvalues_magnitude=0.5, steps=1, assume_real=False):
380-
super().__init__(eigenvalues_magnitude=eigenvalues_magnitude, assume_real=assume_real, name="GenRot", parameter=angle, target=self.extract_targets(generator), control=control)
379+
def __init__(self, angle, generator, p0=None, control=None, target=None, eigenvalues_magnitude=0.5, steps=1, name="GenRot", assume_real=False):
380+
if target == None:
381+
target = self.extract_targets(generator)
382+
super().__init__(eigenvalues_magnitude=eigenvalues_magnitude, generator=generator, assume_real=assume_real, name=name, parameter=angle, target=target, control=control)
381383
self.steps = steps
382-
self.generator = generator
384+
if control is None and p0 is not None:
385+
# augment p0 for control qubits
386+
# Qp = 1/2(1+Z) = |0><0|
387+
p0 = p0*paulis.Qp(control)
388+
self.p0 = p0
389+
390+
def shifted_gates(self):
391+
if not self.assume_real:
392+
# following https://arxiv.org/abs/2104.05695
393+
s = 0.5 * np.pi
394+
shifts = [s, -s, 3 * s, -3 * s]
395+
coeff1 = 0.25 * (np.sqrt(2) + 1)/np.sqrt(2)
396+
coeff2 = 0.25 * (np.sqrt(2) - 1)/np.sqrt(2)
397+
coefficients = [coeff1, -coeff1, -coeff2, coeff2]
398+
circuits = []
399+
for i, shift in enumerate(shifts):
400+
shifted_gate = copy.deepcopy(self)
401+
shifted_gate.parameter += shift
402+
circuits.append((coefficients[i], shifted_gate))
403+
return circuits
383404

405+
r = 0.25
406+
s = 0.5*np.pi
407+
408+
Up1 = copy.deepcopy(self)
409+
Up1._parameter = self.parameter+s
410+
Up2 = GeneralizedRotationImpl(angle=s, generator=self.p0, eigenvalues_magnitude=r) # controls are in p0
411+
Um1 = copy.deepcopy(self)
412+
Um1._parameter = self.parameter-s
413+
Um2 = GeneralizedRotationImpl(angle=-s, generator=self.p0, eigenvalues_magnitude=r) # controls are in p0
414+
415+
return [(2.0 * r, [Up1, Up2]), (-2.0 * r, [Um1, Um2])]
416+
384417
class ExponentialPauliGateImpl(DifferentiableGateImpl):
385418
"""
386419
Same convention as for rotation gates:

Diff for: src/tequila/circuit/gates.py

+90-61
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,13 @@ def Rp(paulistring: typing.Union[PauliString, str], angle, control: typing.Union
361361
return ExpPauli(paulistring=paulistring, angle=angle, control=control, *args, **kwargs)
362362

363363

364+
def GenRot(*args, **kwargs):
365+
return GeneralizedRotation(*args, **kwargs)
366+
364367
def GeneralizedRotation(angle: typing.Union[typing.List[typing.Hashable], typing.List[numbers.Real]],
365368
generator: QubitHamiltonian,
366369
control: typing.Union[list, int] = None,
367-
eigenvalues_magnitude: float = 0.5,
370+
eigenvalues_magnitude: float = 0.5, p0=None,
368371
steps: int = 1, assume_real=False) -> QCircuit:
369372
"""
370373
@@ -393,6 +396,8 @@ def GeneralizedRotation(angle: typing.Union[typing.List[typing.Hashable], typing
393396
list of control qubits
394397
eigenvalues_magnitude
395398
magnitude of eigenvalues, in most papers referred to as "r" (default 0.5)
399+
p0
400+
possible nullspace projector (if the rotation is happens in Q = 1-P0). See arxiv:2011.05938
396401
steps
397402
possible Trotterization steps (default 1)
398403
@@ -403,7 +408,7 @@ def GeneralizedRotation(angle: typing.Union[typing.List[typing.Hashable], typing
403408

404409
return QCircuit.wrap_gate(
405410
impl.GeneralizedRotationImpl(angle=assign_variable(angle), generator=generator, control=control,
406-
eigenvalues_magnitude=eigenvalues_magnitude, steps=steps, assume_real=assume_real))
411+
eigenvalues_magnitude=eigenvalues_magnitude, steps=steps, assume_real=assume_real, p0=p0))
407412

408413

409414

@@ -473,7 +478,7 @@ def Trotterized(generator: QubitHamiltonian = None,
473478
return QCircuit.wrap_gate(impl.TrotterizedGateImpl(generator=generator, angle=angle, steps=steps, control=control, randomize=randomize, **kwargs))
474479

475480

476-
def SWAP(first: int, second: int, control: typing.Union[int, list] = None, power: float = None, *args,
481+
def SWAP(first: int, second: int, angle: float = None, control: typing.Union[int, list] = None, power: float = None, *args,
477482
**kwargs) -> QCircuit:
478483
"""
479484
Notes
@@ -486,10 +491,12 @@ def SWAP(first: int, second: int, control: typing.Union[int, list] = None, power
486491
target qubit
487492
second: int
488493
target qubit
494+
angle: numeric type or hashable type
495+
exponent in the for e^{-i a/2 G}
489496
control
490497
int or list of ints
491498
power
492-
numeric type (fixed exponent) or hashable type (parametrized exponent)
499+
numeric type (fixed exponent) or hashable type (parametrized exponent in the form (SWAP)^n
493500
494501
Returns
495502
-------
@@ -498,12 +505,82 @@ def SWAP(first: int, second: int, control: typing.Union[int, list] = None, power
498505
"""
499506

500507
target = [first, second]
508+
if angle is not None:
509+
assert power is None
510+
angle = assign_variable(angle)
511+
elif power is not None:
512+
angle = assign_variable(power)*np.pi
501513
generator = 0.5 * (paulis.X(target) + paulis.Y(target) + paulis.Z(target) - paulis.I(target))
502-
if power is None or power in [1, 1.0]:
514+
if angle is None or power in [1, 1.0]:
503515
return QGate(name="SWAP", target=target, control=control, generator=generator)
504516
else:
505-
return GeneralizedRotation(angle=power * np.pi, control=control, generator=generator,
517+
return GeneralizedRotation(angle=angle, control=control, generator=generator,
506518
eigenvalues_magnitude=0.25)
519+
520+
521+
def iSWAP(first: int, second: int, control: typing.Union[int, list] = None, power: float = 1.0, *args,
522+
**kwargs) -> QCircuit:
523+
"""
524+
Notes
525+
----------
526+
iSWAP gate
527+
.. math::
528+
iSWAP = e^{i\\frac{\\pi}{4} (X \\otimes X + Y \\otimes Y )}
529+
530+
Parameters
531+
----------
532+
first: int
533+
target qubit
534+
second: int
535+
target qubit
536+
control
537+
int or list of ints
538+
power
539+
numeric type (fixed exponent) or hashable type (parametrized exponent)
540+
541+
Returns
542+
-------
543+
QCircuit
544+
545+
"""
546+
547+
generator = paulis.from_string(f"X({first})X({second}) + Y({first})Y({second})")
548+
549+
p0 = paulis.Projector("|00>") + paulis.Projector("|11>")
550+
p0 = p0.map_qubits({0:first, 1:second})
551+
552+
gate = QubitExcitationImpl(angle=power*(-np.pi/2), target=generator.qubits, generator=generator, p0=p0, control=control, compile_options="vanilla", *args, **kwargs)
553+
554+
return QCircuit.wrap_gate(gate)
555+
556+
557+
def Givens(first: int, second: int, control: typing.Union[int, list] = None, angle: float = None, *args,
558+
**kwargs) -> QCircuit:
559+
"""
560+
Notes
561+
----------
562+
Givens gate G
563+
.. math::
564+
G = e^{-i\\theta \\frac{(Y \\otimes X - X \\otimes Y )}{2}}
565+
566+
Parameters
567+
----------
568+
first: int
569+
target qubit
570+
second: int
571+
target qubit
572+
control
573+
int or list of ints
574+
angle
575+
numeric type (fixed exponent) or hashable type (parametrized exponent), theta in the above formula
576+
577+
Returns
578+
-------
579+
QCircuit
580+
581+
"""
582+
583+
return QubitExcitation(target=[second,first], angle=2*angle, control=control, *args, **kwargs) # twice the angle since theta is not divided by two in the matrix exponential
507584

508585

509586
"""
@@ -965,11 +1042,7 @@ def QGate(name, target: typing.Union[list, int], control: typing.Union[list, int
9651042
returns a QCircuit of primitive tq gates
9661043
"""
9671044

968-
class QubitExcitationImpl(impl.DifferentiableGateImpl):
969-
970-
@property
971-
def steps(self):
972-
return 1
1045+
class QubitExcitationImpl(impl.GeneralizedRotationImpl):
9731046

9741047
def __init__(self, angle, target, generator=None, p0=None, assume_real=True, control=None, compile_options=None):
9751048
angle = assign_variable(angle)
@@ -990,15 +1063,9 @@ def __init__(self, angle, target, generator=None, p0=None, assume_real=True, con
9901063
else:
9911064
assert generator is not None
9921065
assert p0 is not None
993-
994-
super().__init__(name="QubitExcitation", parameter=angle, target=target, control=control)
995-
self.generator = generator
996-
if control is not None:
997-
# augment p0 for control qubits
998-
# Qp = 1/2(1+Z) = |0><0|
999-
p0 = p0*paulis.Qp(control)
1000-
self.p0 = p0
1001-
self.assume_real = assume_real
1066+
1067+
super().__init__(name="QubitExcitation", angle=angle, generator=generator, target=target, p0=p0, control=control, assume_real=assume_real, steps=1)
1068+
10021069
if compile_options is None:
10031070
self.compile_options = "optimize"
10041071
elif hasattr(compile_options, "lower"):
@@ -1007,18 +1074,9 @@ def __init__(self, angle, target, generator=None, p0=None, assume_real=True, con
10071074
self.compile_options = compile_options
10081075

10091076
def map_qubits(self, qubit_map: dict):
1010-
mapped_generator = self.generator.map_qubits(qubit_map=qubit_map)
1011-
mapped_p0 = self.p0.map_qubits(qubit_map=qubit_map)
1012-
mapped_control = self.control
1013-
if mapped_control is not None:
1014-
mapped_control=tuple([qubit_map[i] for i in self.control])
1015-
result = copy.deepcopy(self)
1016-
result.generator=mapped_generator
1017-
result.p0 = mapped_p0
1018-
result._target = tuple([qubit_map[x] for x in self.target])
1019-
result._control = mapped_control
1020-
result.finalize()
1021-
return result
1077+
mapped = super().map_qubits(qubit_map)
1078+
mapped.p0 = self.p0.map_qubits(qubit_map=qubit_map)
1079+
return mapped
10221080

10231081
def compile(self, exponential_pauli=False, *args, **kwargs):
10241082
# optimized compiling for single and double qubit excitaitons following arxiv:2005.14475
@@ -1040,35 +1098,6 @@ def compile(self, exponential_pauli=False, *args, **kwargs):
10401098
else:
10411099
return Trotterized(angle=self.parameter, generator=self.generator, steps=1)
10421100

1043-
def shifted_gates(self):
1044-
if not self.assume_real:
1045-
# following https://arxiv.org/abs/2104.05695
1046-
s = 0.5 * np.pi
1047-
shifts = [s, -s, 3 * s, -3 * s]
1048-
coeff1 = 0.25 * (np.sqrt(2) + 1)/np.sqrt(2)
1049-
coeff2 = 0.25 * (np.sqrt(2) - 1)/np.sqrt(2)
1050-
coefficients = [coeff1, -coeff1, -coeff2, coeff2]
1051-
circuits = []
1052-
for i, shift in enumerate(shifts):
1053-
shifted_gate = copy.deepcopy(self)
1054-
shifted_gate.parameter += shift
1055-
circuits.append((coefficients[i], shifted_gate))
1056-
return circuits
1057-
1058-
r = 0.25
1059-
s = 0.5*np.pi
1060-
1061-
Up1 = copy.deepcopy(self)
1062-
Up1._parameter = self.parameter+s
1063-
Up1 = QCircuit.wrap_gate(Up1)
1064-
Up2 = GeneralizedRotation(angle=s, generator=self.p0, eigenvalues_magnitude=r) # controls are in p0
1065-
Um1 = copy.deepcopy(self)
1066-
Um1._parameter = self.parameter-s
1067-
Um1 = QCircuit.wrap_gate(Um1)
1068-
Um2 = GeneralizedRotation(angle=-s, generator=self.p0, eigenvalues_magnitude=r) # controls are in p0
1069-
1070-
return [(2.0 * r, Up1 + Up2), (-2.0 * r, Um1 + Um2)]
1071-
10721101
def _convert_Paulistring(paulistring: typing.Union[PauliString, str, dict]) -> PauliString:
10731102
'''
10741103
Function that given a paulistring as PauliString structure or

0 commit comments

Comments
 (0)