From e52d13d8a2a0a5bfcea44f7a88a6fe9b95d65833 Mon Sep 17 00:00:00 2001 From: Oliver Huettenhofer Date: Thu, 31 Oct 2024 09:17:44 +0100 Subject: [PATCH 1/2] Fix current Qiskit versions --- README.md | 7 +- src/tequila/simulators/simulator_qiskit.py | 103 +++++++++++++-------- src/tequila/utils/bitstrings.py | 4 +- 3 files changed, 65 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 7cf14d64..ce918c75 100644 --- a/README.md +++ b/README.md @@ -342,12 +342,7 @@ You can avoid it by downgrading cirq and openfermion ```bash pip install --upgrade "openfermion<=1.0.0" pip install --upgrade "cirq<=0.9.1" -``` - - -## Qiskit backend -Qiskit version 0.25 is not yet supported. -`pip install --upgrade qiskit<0.25` fixes potential issues. If not: Please let us know. +``` ## Circuit drawing Standard graphical circuit representation within a Jupyter environment is often done using `tq.draw`. diff --git a/src/tequila/simulators/simulator_qiskit.py b/src/tequila/simulators/simulator_qiskit.py index 9d2130aa..2f292334 100755 --- a/src/tequila/simulators/simulator_qiskit.py +++ b/src/tequila/simulators/simulator_qiskit.py @@ -1,22 +1,26 @@ from tequila.simulators.simulator_base import BackendCircuit, QCircuit, BackendExpectationValue +from tequila.utils.bitstrings import reverse_int_bits from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction from tequila import TequilaException, TequilaWarning from tequila import BitString, BitNumbering, BitStringLSB from tequila.utils.keymap import KeyMapRegisterToSubregister from tequila.utils import to_float -import qiskit, numpy, warnings +import warnings +import numpy as np +import qiskit, qiskit_aer, qiskit.providers.fake_provider -HAS_NOISE=True +HAS_NOISE = True try: - from qiskit_aer import noise as qiskitnoise + from qiskit_aer import noise as qiskitnoise, AerSimulator except: HAS_NOISE = False -HAS_IBMQ=True +HAS_IBMQ = True try: - from qiskit.providers.ibmq import IBMQBackend + from qiskit_ibm_runtime import IBMBackend except: - HAS_IBMQ=False + HAS_IBMQ = False + def get_bit_flip(p): """ @@ -70,16 +74,24 @@ def get_phase_flip(p): 'multicontrol': 3 } -full_basis = ['x', 'y', 'z', 'id', 'u1', 'u2', 'u3', 'h','unitary','sx', +full_basis = ['x', 'y', 'z', 'id', 'u1', 'u2', 'u3', 'h', 'unitary', 'sx', 'cx', 'cy', 'cz', 'cu3', 'ccx'] + def qiskit_device_dict(): - devices = {} - devices.update({str(x).lower():x for x in qiskit.Aer.backends()}) - devices.update({str(x).lower():x for x in qiskit.test.mock.FakeProvider().backends()}) + # As of Quiskit Aer 0.15.0, backends() also returns legacy backends which are not AerSimulators that will be + # deprecated in the future + # TODO: Instead of building a dict with all backends just to search it, search directly with `get_backend` + # https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.AerProvider.html#qiskit_aer.AerProvider.get_backend + devices = {backend.name.lower(): backend for backend in qiskit_aer.AerProvider().backends() + if isinstance(backend, AerSimulator)} + + # FakeProvider has been removed, see https://github.com/Qiskit/qiskit/issues/10954 + # devices.update({str(x).lower(): x for x in qiskit.test.mock.FakeProvider().backends()}) return devices + class TequilaQiskitException(TequilaException): def __str__(self): return "Error in qiskit backend:" + self.message @@ -125,6 +137,8 @@ class BackendCircuitQiskit(BackendCircuit): transform a tequila NoiseModel into a qiskit noise model. """ + STATEVECTOR_DEVICE_NAME = "aer_simulator_statevector" + compiler_arguments = { "trotterized": True, "swap": False, @@ -145,7 +159,7 @@ class BackendCircuitQiskit(BackendCircuit): } numbering = BitNumbering.LSB - + def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise=None, device=None, *args, **kwargs): """ @@ -188,8 +202,9 @@ def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise= if qubit_map is None: qubit_map = {q: i for i, q in enumerate(abstract_circuit.qubits)} else: - warnings.warn("reveived custom qubit_map = {}\n" - "This is not fully integrated with qiskit and might result in unexpected behaviour".format(qubit_map), TequilaWarning) + warnings.warn(f"reveived custom qubit_map = {qubit_map}\n" + "This is not fully integrated with qiskit and might result in unexpected behaviour", + TequilaWarning) n_qubits = max(qubit_map.values()) + 1 @@ -201,7 +216,7 @@ def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise= self.classical_map = self.make_classical_map(qubit_map=self.qubit_map) - if noise != None: + if noise is not None: self.noise_lookup = { 'phase damp': qiskitnoise.phase_damping_error, 'amplitude damp': qiskitnoise.amplitude_damping_error, @@ -211,12 +226,12 @@ def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise= 'depolarizing': qiskitnoise.depolarizing_error } - if isinstance(noise, str): #string noise means "use the same noise as the device I tell you to get." + if isinstance(noise, str): # string noise means "use the same noise as the device I tell you to get." try: self.check_device(noise) self.noise_model = qiskitnoise.NoiseModel.from_backend(noise) except TequilaQiskitException: - raise TequilaException("noise init from string requires that noise names a device. Got {}".format(noise)) + raise TequilaException(f"noise init from string requires that noise names a device. Got {noise}") else: self.noise_model = self.noise_model_converter(noise) @@ -235,7 +250,7 @@ def make_qubit_map(self, qubits: dict = None): qubit_map = super().make_qubit_map(qubits=qubits) mapped_qubits = [q.number for q in qubit_map.values()] for k, v in qubit_map.items(): - qubit_map[k].instance = self.q [v.number] + qubit_map[k].instance = self.q[v.number] return qubit_map @@ -266,10 +281,12 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF """ if self.noise_model is None: if self.device is None: - qiskit_backend = self.retrieve_device('statevector_simulator') + qiskit_backend = self.retrieve_device(self.STATEVECTOR_DEVICE_NAME) else: if 'statevector' not in str(self.device): - raise TequilaException('For simulation, only state vector simulators are supported; recieved device={}, you might have forgoten to set the samples keyword - e.g. (device={}, samples=1000). If not set, tequila assumes that full wavefunction simualtion is demanded which is not compatible with qiskit devices or fake devices except for device=statevector'.format(self.device, self.device)) + raise TequilaException( + 'For simulation, only state vector simulators are supported; recieved device={}, you might have forgoten to set the samples keyword - e.g. (device={}, samples=1000). If not set, tequila assumes that full wavefunction simualtion is demanded which is not compatible with qiskit devices or fake devices except for device=statevector'.format( + self.device, self.device)) else: qiskit_backend = self.retrieve_device(self.device) else: @@ -279,22 +296,24 @@ def do_simulate(self, variables, initial_state=0, *args, **kwargs) -> QubitWaveF if "optimization_level" in kwargs: optimization_level = kwargs['optimization_level'] - opts = {} + circuit = self.circuit.assign_parameters(self.resolver) + if initial_state != 0: - array = numpy.zeros(shape=[2 ** self.n_qubits]) - i = BitStringLSB.from_binary(BitString.from_int(integer=initial_state, nbits=self.n_qubits).binary) - print(initial_state, " -> ", i) - array[i.integer] = 1.0 - opts = {"initial_statevector": array} + state = np.zeros(2 ** self.n_qubits) + initial_state = reverse_int_bits(initial_state, self.n_qubits) + state[initial_state] = 1.0 + init_circuit = qiskit.QuantumCircuit(self.q, self.c) + init_circuit.set_statevector(state) + circuit = init_circuit.compose(circuit) - circuit = self.circuit.bind_parameters(self.resolver) + circuit.save_statevector() - qiskit_job = qiskit_backend.run(circuit,optimization_level=optimization_level,**opts) + backend_result = qiskit_backend.run(circuit, optimization_level=optimization_level).result() - backend_result = qiskit_job.result() return QubitWaveFunction.from_array(arr=backend_result.get_statevector(circuit), numbering=self.numbering) - def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubits, *args, **kwargs) -> QubitWaveFunction: + def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubits, *args, + **kwargs) -> QubitWaveFunction: """ Helper function for performing sampling. Parameters @@ -319,17 +338,18 @@ def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubit else: qiskit_backend = self.retrieve_device(self.device) - if isinstance(qiskit_backend,IBMQBackend): + if isinstance(qiskit_backend, IBMBackend): if self.noise_model is not None: - raise TequilaException('Cannot combine backend {} with custom noise models.'.format(str(qiskit_backend))) - circuit = circuit.bind_parameters(self.resolver) # this is necessary in spite of qiskit "fixing" it + raise TequilaException( + 'Cannot combine backend {} with custom noise models.'.format(str(qiskit_backend))) + circuit = circuit.assign_parameters(self.resolver) # this is necessary in spite of qiskit "fixing" it circuit = qiskit.transpile(circuit, qiskit_backend) - return self.convert_measurements(qiskit_backend.run(circuit,shots=samples, - optimization_level=optimization_level), + return self.convert_measurements(qiskit_backend.run(circuit, shots=samples, + optimization_level=optimization_level), target_qubits=read_out_qubits) else: - if isinstance(qiskit_backend, qiskit.test.mock.FakeBackend): - circuit = circuit.bind_parameters(self.resolver) # this is necessary in spite of qiskit "fixing" it + if isinstance(qiskit_backend, qiskit.providers.fake_provider.FakeBackend): + circuit = circuit.assign_parameters(self.resolver) # this is necessary in spite of qiskit "fixing" it coupling_map = qiskit_backend.configuration().coupling_map from_back = qiskitnoise.NoiseModel.from_backend(qiskit_backend) if self.noise_model is not None: @@ -343,15 +363,15 @@ def do_sample(self, circuit: qiskit.QuantumCircuit, samples: int, read_out_qubit optimization_level=optimization_level ) - job=qiskit_backend.run(circuit, shots=samples) - return self.convert_measurements(job,target_qubits=read_out_qubits) + job = qiskit_backend.run(circuit, shots=samples) + return self.convert_measurements(job, target_qubits=read_out_qubits) else: if self.noise_model is not None: qiskit_backend.set_options(noise_model=self.noise_model) # fits better with our methodology. use_basis = full_basis else: use_basis = qiskit_backend.configuration().basis_gates - circuit = circuit.bind_parameters(self.resolver) # this is necessary -- see qiskit-aer issue 1346 + circuit = circuit.assign_parameters(self.resolver) # this is necessary -- see qiskit-aer issue 1346 circuit = qiskit.transpile(circuit, backend=qiskit_backend, basis_gates=use_basis, optimization_level=optimization_level @@ -436,7 +456,8 @@ def add_parametrized_gate(self, gate, circuit, *args, **kwargs): par = float(gate.parameter) if gate.is_controlled(): if len(gate.control) > 2: - raise TequilaQiskitException("multi-controls beyond 2 not yet supported for the qiskit backend. Gate was:\n{}".format(gate) ) + raise TequilaQiskitException( + "multi-controls beyond 2 not yet supported for the qiskit backend. Gate was:\n{}".format(gate)) ops[1](circuit)(par, self.qubit(gate.control[0]), self.qubit(gate.target[0])) else: ops[0](circuit)(par, self.qubit(gate.target[0])) @@ -590,7 +611,7 @@ def check_device(self, device): if device is None: return - elif isinstance(device,qiskit.providers.Backend): + elif isinstance(device, qiskit.providers.Backend): return elif isinstance(device, dict): diff --git a/src/tequila/utils/bitstrings.py b/src/tequila/utils/bitstrings.py index 92c3aa9c..d725aacc 100644 --- a/src/tequila/utils/bitstrings.py +++ b/src/tequila/utils/bitstrings.py @@ -178,7 +178,7 @@ def numbering(self) -> BitNumbering: return BitNumbering.LSB -def _reverse_int_bits(x: int, nbits: int) -> int: +def reverse_int_bits(x: int, nbits: int) -> int: if nbits is None: nbits = x.bit_length() assert nbits <= 32 @@ -193,7 +193,7 @@ def _reverse_int_bits(x: int, nbits: int) -> int: def initialize_bitstring(integer: int, nbits: int = None, numbering_in: BitNumbering = BitNumbering.MSB, numbering_out: BitNumbering = BitNumbering.MSB): - integer = _reverse_int_bits(integer, nbits) if numbering_in != numbering_out else integer + integer = reverse_int_bits(integer, nbits) if numbering_in != numbering_out else integer if numbering_out == BitNumbering.MSB: return BitString.from_int(integer=integer, nbits=nbits) else: From e9a385f080f4eff07cb44e91670cf6f345c3a68b Mon Sep 17 00:00:00 2001 From: "J. S. Kottmann" Date: Thu, 31 Oct 2024 10:32:45 +0100 Subject: [PATCH 2/2] Update ci_backends.yml --- .github/workflows/ci_backends.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_backends.yml b/.github/workflows/ci_backends.yml index 9ae5d9ab..79717dbe 100644 --- a/.github/workflows/ci_backends.yml +++ b/.github/workflows/ci_backends.yml @@ -32,7 +32,7 @@ jobs: # myqlm does not work in python3.7 pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1" elif [ $ver -eq 8 ]; then - pip install "cirq" "qiskit>=0.30" "qulacs" "qibo==0.1.1" "myqlm" "cirq-google" + pip install "cirq" "qiskit" "qulacs" "qibo==0.1.1" "myqlm" "cirq-google" fi pip install -e . - name: Lint with flake8