Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Devel #169

Merged
merged 12 commits into from
Aug 6, 2021
7 changes: 6 additions & 1 deletion .github/workflows/ci_backends.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install "cirq" "qiskit<0.25" "qulacs" "qibo==0.1.1"
if [ $ver -eq 7 ]; then
# myqlm does not work in python3.7
pip install "cirq" "qiskit<0.25" "qulacs" "qibo==0.1.1"
elif [ $ver -eq 8 ]; then
pip install "cirq" "qiskit<0.25" "qulacs" "qibo==0.1.1" "myqlm"
fi
pip install -e .
- name: Lint with flake8
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci_chemistry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jobs:
python -m pip install -r requirements.txt
python -m pip install -e .
python -m pip install pyscf
python -m pip install 'h5py <= 3.1'
cd tests
ls
pytest test_chemistry.py
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Currently supported
- [Qiskit](https://github.com/qiskit/qiskit) -- currently needs to be qiskit<0.25
- [Cirq](https://github.com/quantumlib/cirq)
- [PyQuil](https://github.com/rigetti/pyquil)
- [QLM](https://atos.net/en/solutions/quantum-learning-machine) (works also whith [myQLM](https://myqlm.github.io/index.html))

Tequila detects backends automatically if they are installed on your systems.
All of them are available over standard pip installation like for example `pip install qulacs`.
Expand All @@ -38,6 +39,9 @@ Currently you need to compile from a separate [fork](https://github.com/kottmanj
See the github page of this fork for installation instruction.
Here is a small [tutorial](https://github.com/aspuru-guzik-group/tequila-tutorials/blob/main/ChemistryMadnessInterface.ipynb) that illustrates the usage.

- [PySCF](https://github.com/pyscf/pyscf)
Works similar as Psi4. Classical methods are also integrated in the madness interface allowing to use them in a basis-set-free representation.

# Install from source
**Do not** install like this: (Minecraft lovers excluded)
<strike>`pip install tequila`</strike>
Expand Down Expand Up @@ -179,8 +183,8 @@ Natural Evolutionary Strategies for Variational Quantum Computation.
J. S. Kottmann, A. Aspuru-Guzik,
Optimized Low-Depth Quantum Circuits for Molecular Electronic Structure using a Separable Pair Approximation,
[arxiv.org/abs/2105.03836](https://arxiv.org/abs/2105.03836)
[example code](https://github.com/aspuru-guzik-group/tequila-tutorials/blob/main/ChemistrySeparablePairAnsatz.ipynb)

[example code](https://github.com/aspuru-guzik-group/tequila-tutorials/blob/main/ChemistrySeparablePairAnsatz.ipynb)
K. Choudhary,
Quantum Computation for Predicting Electron and Phonon Properties of Solids
[arxiv.org/abs/2102.11452](https://arxiv.org/abs/2102.11452)
Expand Down
9 changes: 5 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ scipy >= 1.5 # can in principle be smaller, we recommend >= 1.5 for consistency
sympy
jax
jaxlib
#autograd # use if jaxlib gives you trouble (e.g. on Windows), make sure jax and jaxlib are uninstalled and install autograd
setuptools
# autograd # use if jaxlib gives you trouble (e.g. on Windows), make sure jax and jaxlib are uninstalled and install autograd
setuptools
pytest
openfermion >= 1.0*
cmake # needed by qulacs, can be removed otherwise
Expand All @@ -19,9 +19,10 @@ qulacs # default simulator (best integration), remove if the installation gives
#qibo <= 0.1.1 # can not be installed in the same environment as gpyopt

#optional optimizers
#phoenics # version on PyPi isc urrently broken, we recommend to install from source (AAG github)
#phoenics # version on PyPi isc urrently broken, we recommend to install from source (AAG github)
#gpyopt # not in combination with qibo as quantum backend

#optional third party libraries
#pyzx

#pyscf # if used also restrict h5py version untill the issue is fixed upstream in pyscf
#h5py <= 3.1 # version 3.3.0 leads to crashes when pyscf is imported
34 changes: 25 additions & 9 deletions src/tequila/circuit/_gates_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from tequila.objective.objective import Variable, FixedVariable, assign_variable,Objective,VectorObjective
from tequila.hamiltonian import PauliString, QubitHamiltonian, paulis
from tequila.tools import list_assignment
from numpy import pi
from numpy import pi, sqrt

from dataclasses import dataclass

Expand Down Expand Up @@ -61,6 +61,13 @@ def make_generator(self, include_controls=False):

return self.generator

def map_variables(self, variables):

if self.is_parametrized():
self.parameter=self.parameter.map_variables(variables)

return self

def __init__(self, name, target: UnionList, control: UnionList = None, generator: QubitHamiltonian = None):
self._name = name
self._target = tuple(list_assignment(target))
Expand Down Expand Up @@ -184,7 +191,7 @@ def __str__(self):
if not self.is_single_qubit_gate():
result += ", control=" + str(self.control)

result += ", parameter=" + str(self._parameter)
result += ", parameter=" + repr(self._parameter)
result += ")"
return result

Expand Down Expand Up @@ -228,6 +235,19 @@ def shifted_gates(self, r=None):
r = self.eigenvalues_magnitude

s = pi / (4 * r)
if self.is_controlled() and not self.assume_real:
# following https://arxiv.org/abs/2104.05695
shifts = [s, -s, 3 * s, -3 * s]
coeff1 = (sqrt(2) + 1)/sqrt(8) * r
coeff2 = (sqrt(2) - 1)/sqrt(8) * r
coefficients = [coeff1, -coeff1, -coeff2, coeff2]
circuits = []
for i, shift in enumerate(shifts):
shifted_gate = copy.deepcopy(self)
shifted_gate.parameter += shift
circuits.append((coefficients[i], shifted_gate))
return circuits

shift_a = self.parameter + s
shift_b = self.parameter - s
right = copy.deepcopy(self)
Expand All @@ -240,11 +260,7 @@ def shifted_gates(self, r=None):
p0 = paulis.Qp(self.control) # Qp = |0><0|
right2 = GeneralizedRotationImpl(angle=s, generator=p0, eigenvalues_magnitude=r/2) # controls are in p0
left2 = GeneralizedRotationImpl(angle=-s, generator=p0, eigenvalues_magnitude=r/2) # controls are in p0
if not self.assume_real:
# 4-point shift rule of arxiv:2104.05695 would saves gates here
return [(r/2, [right, right2]), (-r/2, [left , left2]), (r/2, [right , left2]), (-r/2, [left , right2])]
else:
return [(r, [right, right2]), (-r, [left , left2])]
return [(r, [right, right2]), (-r, [left , left2])]
else:
return [ (r, right), (-r, left) ]

Expand Down Expand Up @@ -382,7 +398,7 @@ def __str__(self):
if not self.is_single_qubit_gate():
result += ", control=" + str(self.control)

result += ", parameter=" + str(self.parameter)
result += ", parameter=" + repr(self.parameter)
result += ", paulistring=" + str(self.paulistring)
result += ")"
return result
Expand Down Expand Up @@ -426,7 +442,7 @@ def __str__(self):
if not self.is_single_qubit_gate():
result += ", control=" + str(self.control)

result += ", angle=" + str(self.angle)
result += ", angle=" + repr(self.parameter)
result += ", generator=" + str(self.generator)
result += ")"
return result
Expand Down
150 changes: 139 additions & 11 deletions src/tequila/circuit/circuit.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from tequila.circuit._gates_impl import QGateImpl
from tequila import TequilaException
from __future__ import annotations
from tequila.circuit._gates_impl import QGateImpl, assign_variable, list_assignment
from tequila import TequilaException, TequilaWarning
from tequila import BitNumbering
import typing, copy
import typing
import copy
from collections import defaultdict
import warnings


class QCircuit():
Expand Down Expand Up @@ -157,7 +160,8 @@ def n_qubits(self, other):
self._min_n_qubits = other
if other < self.max_qubit() + 1:
raise TequilaException(
"You are trying to set n_qubits to " + str(other) + " but your circuit needs at least: " + str(
"You are trying to set n_qubits to " + str(
other) + " but your circuit needs at least: " + str(
self.max_qubit() + 1))
return self

Expand Down Expand Up @@ -367,7 +371,6 @@ def is_mixed(self):
return not (self.is_fully_parametrized() or self.is_fully_unparametrized())

def __iadd__(self, other):
# duck typing
other = self.wrap_gate(gate=other)

offset = len(self.gates)
Expand All @@ -380,6 +383,7 @@ def __iadd__(self, other):
return self

def __add__(self, other):
other = self.wrap_gate(other)
gates = [g.copy() for g in (self.gates + other.gates)]
result = QCircuit(gates=gates)
result._min_n_qubits = max(self._min_n_qubits, other._min_n_qubits)
Expand Down Expand Up @@ -452,7 +456,7 @@ def to_networkx(self):
control = int(cstr)
Gdict[p].append(control) # add control to key of correlated targets
else:
for s in gate.target:
for s in gate.target:
for t in gate.target:
tstr2 = ''
tstr2 += str(t)
Expand Down Expand Up @@ -520,6 +524,125 @@ def map_qubits(self, qubit_map):
# currently its recreated in the init function
return QCircuit(gates=new_gates)

def add_controls(self, control, inpl: typing.Optional[bool] = True) \
-> typing.Optional[QCircuit]:
"""Depending on the truth value of inpl:
- return controlled version of self with control as the control qubits if inpl;
- mutate self so that the qubits in control are added as the control qubits if not inpl.

Raise ValueError if there any qubits in common between self and control.
"""
if inpl:
self._inpl_control_circ(control)
else:
# return self._return_control_circ(control)
circ = copy.deepcopy(self)
return circ.add_controls(control, inpl=True)

def _return_control_circ(self, control) -> QCircuit:
"""Return controlled version of self with control as the control qubits.

This is not an in-place method, so it DOES NOT mutates self, and only returns a circuit.

Raise TequilaWarning if there any qubits in common between self and control.
"""
control = list(set(list_assignment(control)))

gates = self.gates
cgates = []

for gate in gates:
cgate = copy.deepcopy(gate)

if cgate.is_controlled():
control_lst = list(set(list(cgate.control) + list(control)))

if len(control_lst) < len(gate.control) + len(control):
# warnings.warn("Some of the controls {} were already included in the control "
# "of a gate {}.".format(control, gate), TequilaWarning)
raise TequilaWarning(f'Some of the controls {control} were already included '
f'in the control of a gate {gate}.')
else:
control_lst, not_control = list(control), list()

# Raise TequilaWarning if target and control are the same qubit
if any(qubit in control for qubit in not_control):
# warnings.warn("The target and control {} were the same qubit for a gate {}."
# .format(control, gate), TequilaWarning)
raise TequilaWarning(f'The target for a gate {gate} '
f'and the control list {control_lst} had a common qubit.')

cgate._control = tuple(control_lst)
cgate.finalize()
cgates.append(cgate)

return QCircuit(gates=cgates)

def _inpl_control_circ(self, control) -> None:
"""Mutate self such that the qubits in control are added as the control qubits

This is an in-place method, so it mutates self and doesn't return any value.

Raise TequilaWarning if there any qubits in common between self and control.
"""
gates = self.gates
control = list_assignment(control)

for gate in gates:
if gate.is_controlled():
control_lst = list(set(list(gate.control) + list(control)))

# Need to check duplicates
not_control = list(set(qubit for qubit in list(gate.qubits)
if qubit not in list(gate.control)))

# Raise TequilaWarning if control qubit is duplicated
if len(control_lst) < len(gate.control) + len(control):
# warnings.warn("Some of the controls {} were already included in the control "
# "of a gate {}.".format(control, gate), TequilaWarning)
raise TequilaWarning(f'Some of the controls {control} were already included '
f'in the control of a gate {gate}.')
else:
control_lst, not_control = list(control), list()

# Raise TequilaWarning if target and control are the same qubit
if any(qubit in control for qubit in not_control):
# warnings.warn("The target and control {} were the same qubit for a gate {}."
# .format(control, gate), TequilaWarning)
raise TequilaWarning(f'The target for a gate {gate} '
f'and the control list {control} had a common qubit.')

gate._control = tuple(control_lst)
gate.finalize()

def map_variables(self, variables: dict, *args, **kwargs):
"""

Parameters
----------
variables
dictionary with old variable names as keys and new variable names or values as values
Returns
-------
Circuit with changed variables

"""

variables = {assign_variable(k): assign_variable(v) for k, v in variables.items()}

# failsafe
my_variables = self.extract_variables()
for k, v in variables.items():
if k not in my_variables:
warnings.warn(
"map_variables: variable {} is not part of circuit with variables {}".format(k,
my_variables),
TequilaWarning)

new_gates = [copy.deepcopy(gate).map_variables(variables) for gate in self.gates]

return QCircuit(gates=new_gates)


class Moment(QCircuit):
"""
Expand Down Expand Up @@ -587,7 +710,8 @@ def __init__(self, gates: typing.List[QGateImpl] = None, sort=False):
for q in g.qubits:
if q in occ:
raise TequilaException(
'cannot have doubly occupied qubits, which occurred at qubit {}'.format(str(q)))
'cannot have doubly occupied qubits, which occurred at qubit {}'.format(
str(q)))
else:
occ.append(q)
if sort:
Expand Down Expand Up @@ -648,7 +772,8 @@ def with_gates(self, gates):
if q not in first_overlap:
first_overlap.append(q)
else:
raise TequilaException('cannot have a moment with multiple operations acting on the same qubit!')
raise TequilaException(
'cannot have a moment with multiple operations acting on the same qubit!')

new = self.with_gate(gl[0])
for g in gl[1:]:
Expand All @@ -675,7 +800,8 @@ def add_gate(self, gate: typing.Union[QCircuit, QGateImpl]):
for n in newq:
if n in prev:
raise TequilaException(
'cannot add gate {} to moment; qubit {} already occupied.'.format(str(gate), str(n)))
'cannot add gate {} to moment; qubit {} already occupied.'.format(str(gate),
str(n)))

self._gates.append(gate)
self.sort_gates()
Expand Down Expand Up @@ -784,7 +910,8 @@ def __add__(self, other):
result._min_n_qubits += len(other.qubits)
except:
result = self.as_circuit() + QCircuit.wrap_gate(other)
result._min_n_qubits = max(self.as_circuit()._min_n_qubits, QCircuit.wrap_gate(other)._min_n_qubits)
result._min_n_qubits = max(self.as_circuit()._min_n_qubits,
QCircuit.wrap_gate(other)._min_n_qubits)

else:
if isinstance(other, QGateImpl):
Expand All @@ -793,7 +920,8 @@ def __add__(self, other):
result._min_n_qubits += len(other.qubits)
except:
result = self.as_circuit() + QCircuit.wrap_gate(other)
result._min_n_qubits = max(self.as_circuit()._min_n_qubits, QCircuit.wrap_gate(other)._min_n_qubits)
result._min_n_qubits = max(self.as_circuit()._min_n_qubits,
QCircuit.wrap_gate(other)._min_n_qubits)
else:
raise TequilaException(
'cannot add moments to types other than QCircuit,Moment,or Gate; recieved summand of type {}'.format(
Expand Down
Loading