diff --git a/Makefile b/Makefile index f28e6c7..ba35a66 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ include subtrees/z_quantum_actions/Makefile github_actions: python3 -m venv ${VENV} && \ ${VENV}/bin/python3 -m pip install --upgrade pip && \ ${VENV}/bin/python3 -m pip install ./z-quantum-core && \ - ${VENV}/bin/python3 -m pip install -e '.[develop]' \ No newline at end of file + ${VENV}/bin/python3 -m pip install -e '.[develop]' + +build-system-deps: + $(PYTHON) -m pip install setuptools wheel "setuptools_scm>=6.0" diff --git a/setup.py b/setup.py index 07d7850..d12dbe3 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2022 Zapata Computing Inc. +################################################################################ import site import sys import warnings diff --git a/src/python/qeqiskit/backend/__init__.py b/src/python/qeqiskit/backend/__init__.py index 73467c7..c98d062 100644 --- a/src/python/qeqiskit/backend/__init__.py +++ b/src/python/qeqiskit/backend/__init__.py @@ -1 +1,4 @@ +################################################################################ +# © Copyright 2020 Zapata Computing Inc. +################################################################################ from .backend import QiskitBackend diff --git a/src/python/qeqiskit/backend/backend.py b/src/python/qeqiskit/backend/backend.py index 8ccd2aa..67211b0 100644 --- a/src/python/qeqiskit/backend/backend.py +++ b/src/python/qeqiskit/backend/backend.py @@ -1,10 +1,19 @@ +################################################################################ +# © Copyright 2020-2022 Zapata Computing Inc. +################################################################################ import math import time -from typing import List, Optional, Sequence, Tuple +from copy import deepcopy +from typing import Dict, List, Optional, Sequence, Tuple from qeqiskit.conversions import export_to_qiskit from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, execute -from qiskit.ignis.mitigation.measurement import CompleteMeasFitter, complete_meas_cal +from qiskit.circuit.gate import Gate as QiskitGate +from qiskit.ignis.mitigation.measurement import ( + CompleteMeasFitter, + MeasurementFilter, + complete_meas_cal, +) from qiskit.providers.ibmq import IBMQ from qiskit.providers.ibmq.exceptions import IBMQAccountError, IBMQBackendJobLimitError from qiskit.providers.ibmq.job import IBMQJob @@ -27,6 +36,7 @@ def __init__( retry_delay_seconds: Optional[int] = 60, retry_timeout_seconds: Optional[int] = 86400, n_samples_for_readout_calibration: Optional[int] = None, + noise_inversion_method: str = "least_squares", **kwargs, ): """Get a qiskit QPU that adheres to the @@ -50,6 +60,9 @@ def __init__( retry_delay_seconds: Number of seconds to wait to resubmit a job when backend job limit is reached. retry_timeout_seconds: Number of seconds to wait + noise_inversion_method (str): Method for inverting noise using readout + correction. Options are "least_squares" and "pseudo_inverse". + Defaults to "least_squares." """ super().__init__() self.device_name = device_name @@ -69,7 +82,7 @@ def __init__( self.batch_size: int = self.device.configuration().max_experiments self.supports_batching = True self.readout_correction = readout_correction - self.readout_correction_filter = None + self.readout_correction_filters: Dict[str, MeasurementFilter] = {} self.optimization_level = optimization_level self.basis_gates = kwargs.get( "basis_gates", self.device.configuration().basis_gates @@ -77,6 +90,7 @@ def __init__( self.retry_delay_seconds = retry_delay_seconds self.retry_timeout_seconds = retry_timeout_seconds self.n_samples_for_readout_calibration = n_samples_for_readout_calibration + self.noise_inversion_method = noise_inversion_method def run_circuit_and_measure(self, circuit: Circuit, n_samples: int) -> Measurements: """Run a circuit and measure a certain number of bitstrings. @@ -89,6 +103,40 @@ def run_circuit_and_measure(self, circuit: Circuit, n_samples: int) -> Measureme raise ValueError("n_samples should be greater than 0.") return self.run_circuitset_and_measure([circuit], [n_samples])[0] + def run_circuitset_and_measure( + self, + circuits: Sequence[Circuit], + n_samples: Sequence[int], + ) -> List[Measurements]: + """Run a set of circuits and measure a certain number of bitstrings. + + Args: + circuitset: the circuits to run + n_samples: The number of shots to perform on each circuit. + + Returns: + A list of Measurements objects containing the observed bitstrings. + """ + + ( + experiments, + n_samples_for_experiments, + multiplicities, + ) = self.transform_circuitset_to_ibmq_experiments(circuits, n_samples) + batches, n_samples_for_batches = self.batch_experiments( + experiments, n_samples_for_experiments + ) + + jobs = [ + self.execute_with_retries(batch, n_samples) + for n_samples, batch in zip(n_samples_for_batches, batches) + ] + + self.number_of_circuits_run += len(circuits) + self.number_of_jobs_run += len(batches) + + return self.aggregate_measurements(jobs, batches, multiplicities) + def transform_circuitset_to_ibmq_experiments( self, circuitset: Sequence[Circuit], @@ -106,6 +154,7 @@ def transform_circuitset_to_ibmq_experiments( Tuple containing: - The expanded list of circuits, converted to qiskit and each assigned a unique name. + - List of number of samples for each element in expanded list of circuits - An array indicating how many duplicates there are for each of the original circuits. """ @@ -188,7 +237,49 @@ def batch_experiments( return batches, n_samples_for_batches - def aggregregate_measurements( + def execute_with_retries( + self, batch: List[QuantumCircuit], n_samples: int + ) -> IBMQJob: + """Execute a job, resubmitting if the the backend job limit has been + reached. + + The number of seconds between retries is specified by + self.retry_delay_seconds. If self.retry_timeout_seconds is defined, then + an exception will be raised if the submission does not succeed in the + specified number of seconds. + + Args: + batch: The batch of qiskit circuits to be executed. + n_samples: The number of shots to perform on each circuit. + + Returns: + The qiskit representation of the submitted job. + """ + + start_time = time.time() + while True: + try: + job = execute( + batch, + self.device, + shots=n_samples, + basis_gates=self.basis_gates, + optimization_level=self.optimization_level, + backend_properties=self.device.properties(), + ) + return job + except IBMQBackendJobLimitError: + if self.retry_timeout_seconds is not None: + elapsed_time_seconds = time.time() - start_time + if elapsed_time_seconds > self.retry_timeout_seconds: + raise RuntimeError( + f"Failed to submit job in {elapsed_time_seconds}s due to " + "backend job limit." + ) + print(f"Job limit reached. Retrying in {self.retry_delay_seconds}s.") + time.sleep(self.retry_delay_seconds) # type: ignore + + def aggregate_measurements( self, jobs: List[IBMQJob], batches: List[List[QuantumCircuit]], @@ -209,26 +300,37 @@ def aggregregate_measurements( corresponds to one of the circuits of the original (unexpanded) circuit set. """ - ibmq_circuit_counts_set = [] + circuit_set = [] + circuit_counts_set = [] for job, batch in zip(jobs, batches): for experiment in batch: - ibmq_circuit_counts_set.append(job.result().get_counts(experiment)) + circuit_set.append(experiment) + circuit_counts_set.append(job.result().get_counts(experiment)) measurements_set = [] - ibmq_circuit_index = 0 + circuit_index = 0 for multiplicity in multiplicities: combined_counts = Counts({}) - for i in range(multiplicity): - for bitstring, counts in ibmq_circuit_counts_set[ - ibmq_circuit_index - ].items(): + for _ in range(multiplicity): + for bitstring, counts in circuit_counts_set[circuit_index].items(): combined_counts[bitstring] = ( combined_counts.get(bitstring, 0) + counts ) - ibmq_circuit_index += 1 + circuit_index += 1 if self.readout_correction: - combined_counts = self._apply_readout_correction(combined_counts) + current_circuit = circuit_set[circuit_index - 1] + active_qubits = list( + { + qubit.index + for inst in current_circuit.data + if isinstance(inst[0], QiskitGate) + for qubit in inst[1] + } + ) + combined_counts = self._apply_readout_correction( + combined_counts, active_qubits + ) # qiskit counts object maps bitstrings in reversed order to ints, so we must # flip the bitstrings @@ -241,94 +343,54 @@ def aggregregate_measurements( return measurements_set - def run_circuitset_and_measure( + def _apply_readout_correction( self, - circuits: Sequence[Circuit], - n_samples: Sequence[int], - ) -> List[Measurements]: - """Run a set of circuits and measure a certain number of bitstrings. + counts: Counts, + active_qubits: Optional[List[int]] = None, + ): + """Returns the counts from an experiment with readout correction applied to a + set of qubits labeled active_qubits. Output counts will only show outputs for + corrected qubits. If no filter exists for the current active, qubits the + function will make one. Otherwise, function will re-use filter it created + for these active qubits previously. Has 8 digits of precision. Args: - circuitset: the circuits to run - n_samples: The number of shots to perform on each circuit. - - Returns: - A list of Measurements objects containing the observed bitstrings. - """ - - ( - experiments, - n_samples_for_experiments, - multiplicities, - ) = self.transform_circuitset_to_ibmq_experiments(circuits, n_samples) - batches, n_samples_for_batches = self.batch_experiments( - experiments, n_samples_for_experiments - ) - - jobs = [ - self.execute_with_retries(batch, n_samples) - for n_samples, batch in zip(n_samples_for_batches, batches) - ] - - self.number_of_circuits_run += len(circuits) - self.number_of_jobs_run += len(batches) - - return self.aggregregate_measurements(jobs, batches, multiplicities) - - def execute_with_retries( - self, batch: List[QuantumCircuit], n_samples: int - ) -> IBMQJob: - """Execute a job, resubmitting if the the backend job limit has been - reached. + counts (Counts): Dictionary containing the number of times a bitstring + was received in an experiment. + active_qubits (Optional[List[int]], optional): Qubits for perform readout + correction on. Defaults to readout correction on all qubits. - The number of seconds between retries is specified by - self.retry_delay_seconds. If self.retry_timeout_seconds is defined, then - an exception will be raised if the submission does not succeed in the - specified number of seconds. - - Args: - batch: The batch of qiskit ircuits to be executed. - n_samples: The number of shots to perform on each circuit. + Raises: + TypeError: If n_samples_for_readout_correction was not defined when the + QiskitBackend Object was declared. Returns: - The qiskit representation of the submitted job. + mitigated_counts (Counts): counts for each output bitstring only showing + the qubits which were mitigated. """ - start_time = time.time() - while True: - try: - job = execute( - batch, - self.device, - shots=n_samples, - basis_gates=self.basis_gates, - optimization_level=self.optimization_level, - backend_properties=self.device.properties(), - ) - return job - except IBMQBackendJobLimitError: - if self.retry_timeout_seconds is not None: - elapsed_time_seconds = time.time() - start_time - if elapsed_time_seconds > self.retry_timeout_seconds: - raise RuntimeError( - f"Failed to submit job in {elapsed_time_seconds}s due to " - "backend job limit." - ) - print(f"Job limit reached. Retrying in {self.retry_delay_seconds}s.") - time.sleep(self.retry_delay_seconds) # type: ignore + for key in counts.keys(): + num_qubits = len(key) + break - def _apply_readout_correction(self, counts, qubit_list=None): - if self.readout_correction_filter is None: + if active_qubits is None: + active_qubits = list(range(num_qubits)) + else: + active_qubits.sort() + for key in deepcopy(list(counts.keys())): + new_key = "".join(key[i] for i in active_qubits) + counts[new_key] = counts.get(new_key, 0) + counts.pop(key) - for key in counts.keys(): - num_qubits = len(key) - break + if not self.readout_correction_filters.get(str(active_qubits)): - if qubit_list is None or qubit_list == {}: - qubit_list = [i for i in range(num_qubits)] + if self.n_samples_for_readout_calibration is None: + raise TypeError( + "n_samples_for_readout_calibration must" + "be set to use readout calibration" + ) qr = QuantumRegister(num_qubits) - meas_cals, state_labels = complete_meas_cal(qubit_list=qubit_list, qr=qr) + meas_cals, state_labels = complete_meas_cal(qubit_list=active_qubits, qr=qr) # Execute the calibration circuits job = self.execute_with_retries( @@ -338,8 +400,14 @@ def _apply_readout_correction(self, counts, qubit_list=None): # Make a calibration matrix meas_fitter = CompleteMeasFitter(cal_results, state_labels) - # Create a measurement filter from the calibration matrix - self.readout_correction_filter = meas_fitter.filter - mitigated_counts = self.readout_correction_filter.apply(counts) - return mitigated_counts + # Create a measurement filter from the calibration matrix + self.readout_correction_filters[str(active_qubits)] = meas_fitter.filter + + this_filter = self.readout_correction_filters[str(active_qubits)] + mitigated_counts = this_filter.apply(counts, method=self.noise_inversion_method) + # round to make up for precision loss from pseudoinverses used to invert noise + rounded_mitigated_counts = { + k: round(v, 8) for k, v in mitigated_counts.items() if round(v, 8) != 0 + } + return rounded_mitigated_counts diff --git a/src/python/qeqiskit/conversions/__init__.py b/src/python/qeqiskit/conversions/__init__.py index 9849807..bfde4bc 100644 --- a/src/python/qeqiskit/conversions/__init__.py +++ b/src/python/qeqiskit/conversions/__init__.py @@ -1,2 +1,5 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ from ._circuit_conversions import export_to_qiskit, import_from_qiskit from ._openfermion_conversions import qiskitpauli_to_qubitop, qubitop_to_qiskitpauli diff --git a/src/python/qeqiskit/conversions/_circuit_conversions.py b/src/python/qeqiskit/conversions/_circuit_conversions.py index d2f41b4..c0adc00 100644 --- a/src/python/qeqiskit/conversions/_circuit_conversions.py +++ b/src/python/qeqiskit/conversions/_circuit_conversions.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import hashlib from typing import Dict, Iterable, List, NamedTuple, Sequence, Tuple, Union diff --git a/src/python/qeqiskit/conversions/_openfermion_conversions.py b/src/python/qeqiskit/conversions/_openfermion_conversions.py index 85ef5e3..82ef6c4 100644 --- a/src/python/qeqiskit/conversions/_openfermion_conversions.py +++ b/src/python/qeqiskit/conversions/_openfermion_conversions.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ ############################################################################ # Copyright 2017 Rigetti Computing, Inc. # Modified by Zapata Computing 2020 to work for qiskit's SummedOp. diff --git a/src/python/qeqiskit/conversions/_qiskit_expressions.py b/src/python/qeqiskit/conversions/_qiskit_expressions.py index 3fc7125..9b5eddb 100644 --- a/src/python/qeqiskit/conversions/_qiskit_expressions.py +++ b/src/python/qeqiskit/conversions/_qiskit_expressions.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ """Translations between Qiskit parameter expressions and intermediate expression trees. Attributes: diff --git a/src/python/qeqiskit/conversions/_symengine_expressions.py b/src/python/qeqiskit/conversions/_symengine_expressions.py index fd7b121..f8c6269 100644 --- a/src/python/qeqiskit/conversions/_symengine_expressions.py +++ b/src/python/qeqiskit/conversions/_symengine_expressions.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ """Utilities for converting symengine expressions to our native Expression format.""" import operator from functools import singledispatch diff --git a/src/python/qeqiskit/noise/__init__.py b/src/python/qeqiskit/noise/__init__.py index 624aa5a..e5f97ff 100644 --- a/src/python/qeqiskit/noise/__init__.py +++ b/src/python/qeqiskit/noise/__init__.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2021 Zapata Computing Inc. +################################################################################ from .basic import ( create_amplitude_damping_noise, create_phase_and_amplitude_damping_error, diff --git a/src/python/qeqiskit/noise/basic.py b/src/python/qeqiskit/noise/basic.py index dc82579..ac7d4d5 100644 --- a/src/python/qeqiskit/noise/basic.py +++ b/src/python/qeqiskit/noise/basic.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2021 Zapata Computing Inc. +################################################################################ from typing import Dict, Optional, Tuple import numpy as np diff --git a/src/python/qeqiskit/optimizer/__init__.py b/src/python/qeqiskit/optimizer/__init__.py index 32f6798..0c4f362 100644 --- a/src/python/qeqiskit/optimizer/__init__.py +++ b/src/python/qeqiskit/optimizer/__init__.py @@ -1 +1,4 @@ +################################################################################ +# © Copyright 2020-2021 Zapata Computing Inc. +################################################################################ from .optimizer import QiskitOptimizer diff --git a/src/python/qeqiskit/optimizer/optimizer.py b/src/python/qeqiskit/optimizer/optimizer.py index 28eb889..7168d58 100644 --- a/src/python/qeqiskit/optimizer/optimizer.py +++ b/src/python/qeqiskit/optimizer/optimizer.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2022 Zapata Computing Inc. +################################################################################ from typing import Callable, Dict, Optional, Union import numpy as np diff --git a/src/python/qeqiskit/simulator/__init__.py b/src/python/qeqiskit/simulator/__init__.py index 37bb3e9..09f97d7 100644 --- a/src/python/qeqiskit/simulator/__init__.py +++ b/src/python/qeqiskit/simulator/__init__.py @@ -1 +1,4 @@ +################################################################################ +# © Copyright 2020 Zapata Computing Inc. +################################################################################ from .simulator import QiskitSimulator diff --git a/src/python/qeqiskit/simulator/simulator.py b/src/python/qeqiskit/simulator/simulator.py index f8aea6a..ceed7b4 100644 --- a/src/python/qeqiskit/simulator/simulator.py +++ b/src/python/qeqiskit/simulator/simulator.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2022 Zapata Computing Inc. +################################################################################ import sys from numbers import Complex from typing import List, Optional diff --git a/src/python/qeqiskit/utils.py b/src/python/qeqiskit/utils.py index fc5958f..87cba77 100644 --- a/src/python/qeqiskit/utils.py +++ b/src/python/qeqiskit/utils.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2021 Zapata Computing Inc. +################################################################################ import json from typing import TextIO diff --git a/steps/noise.py b/steps/noise.py index 20ffd00..0fcca69 100644 --- a/steps/noise.py +++ b/steps/noise.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2020-2021 Zapata Computing Inc. +################################################################################ from qeqiskit.noise import ( create_amplitude_damping_noise as _create_amplitude_damping_noise, ) diff --git a/subtrees/z_quantum_actions/Makefile b/subtrees/z_quantum_actions/Makefile index 0f606cd..d23a543 100644 --- a/subtrees/z_quantum_actions/Makefile +++ b/subtrees/z_quantum_actions/Makefile @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))) include $(TOP_DIR)/variables.mk @@ -82,6 +85,9 @@ style-default: flake8 mypy black isort muster-default: style coverage @echo This project passes muster! +build-system-deps-default: + : + # This is what converts the -default targets into base target names. # Do not remove!!! %: %-default diff --git a/subtrees/z_quantum_actions/actions/publish-release/action.yml b/subtrees/z_quantum_actions/actions/publish-release/action.yml index 1d88650..11877b1 100644 --- a/subtrees/z_quantum_actions/actions/publish-release/action.yml +++ b/subtrees/z_quantum_actions/actions/publish-release/action.yml @@ -34,12 +34,46 @@ runs: git push --tags echo "::set-output name=tag::$TAG" + # Use pip to build a PEP 517 wheel + - name: Build wheel + id: build-wheel + shell: bash + run: python3 -m pip wheel --isolated --no-deps -w dist . + + # Upload all artifacts in dist + - name: Store wheel artifacts + uses: actions/upload-artifact@v2 + with: + name: Python Wheels + path: dist + - name: Create entry on GH Releases + id: make-release shell: bash run: | - curl \ + # When we make a release, we should check the response for the "upload_url" + API_RESPONSE=$(curl \ -X POST \ - curl -H "Authorization: token ${{ github.token }}" \ + -H "Authorization: token ${{ github.token }}" \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/${{ github.repository }}/releases \ - -d '{"tag_name":"${{ steps.push-new-version-tag.outputs.tag }}"}' + -d '{"tag_name":"${{ steps.push-new-version-tag.outputs.tag }}"}') + # The URL from github has some extras in curly brackets that we do not want: + # The URL always ends in assets, so we can use that to help check for the end + UPLOAD_URL=$(echo ${API_RESPONSE} | jq .upload_url | sed "s/\(.*assets\){.*}/\1/") + echo "::set-output name=upload_url::${UPLOAD_URL}" + + - name: Upload wheel to release page + shell: bash + run: | + # We don't know the wheel name, so we can check for everything in the dist dir + # We may build multiple wheels from a single repo, so this works for that too. + for wheel in dist/*; do + curl \ + -X POST \ + -H "Authorization: token ${{ github.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Content-Type: $(file -b --mime-type $wheel)" \ + -T "$wheel" \ + "${{ steps.make-release.outputs.upload_url }}?name=$(basename $wheel)" + done diff --git a/subtrees/z_quantum_actions/bin/get_next_version.py b/subtrees/z_quantum_actions/bin/get_next_version.py index c020a20..c646611 100755 --- a/subtrees/z_quantum_actions/bin/get_next_version.py +++ b/subtrees/z_quantum_actions/bin/get_next_version.py @@ -1,4 +1,7 @@ #! /usr/bin/env python3 +################################################################################ +# © Copyright 2022 Zapata Computing Inc. +################################################################################ # This script ia intended to be run from within a Github Action. # Reads current project version, bumps "minor", and sets the "next_version" output diff --git a/subtrees/z_quantum_actions/sample_setup.py b/subtrees/z_quantum_actions/sample_setup.py index 80f8e3c..f6e9ff2 100644 --- a/subtrees/z_quantum_actions/sample_setup.py +++ b/subtrees/z_quantum_actions/sample_setup.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import site import sys diff --git a/subtrees/z_quantum_actions/setup_extras.py b/subtrees/z_quantum_actions/setup_extras.py index ed1d1da..dc38637 100644 --- a/subtrees/z_quantum_actions/setup_extras.py +++ b/subtrees/z_quantum_actions/setup_extras.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ extras = { # Development extras needed in every project, because the stylechecks depend on it. # If you need more dev deps, extend this list in your own setup.py. diff --git a/subtrees/z_quantum_actions/tests/test_get_next_version.py b/subtrees/z_quantum_actions/tests/test_get_next_version.py index cb9614f..a17fd3a 100644 --- a/subtrees/z_quantum_actions/tests/test_get_next_version.py +++ b/subtrees/z_quantum_actions/tests/test_get_next_version.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2022 Zapata Computing Inc. +################################################################################ import re import subprocess diff --git a/tests/qeqiskit/backend/backend_test.py b/tests/qeqiskit/backend/backend_test.py index 949041f..e564e0d 100644 --- a/tests/qeqiskit/backend/backend_test.py +++ b/tests/qeqiskit/backend/backend_test.py @@ -1,5 +1,9 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import math import os +from copy import deepcopy import pytest import qiskit @@ -33,7 +37,15 @@ def backend(request): "n_samples_for_readout_calibration": 1, "retry_delay_seconds": 1, }, - ] + { + "device_name": "ibmq_qasm_simulator", + "api_token": os.getenv("ZAPATA_IBMQ_API_TOKEN"), + "readout_correction": True, + "n_samples_for_readout_calibration": 1, + "retry_delay_seconds": 1, + "noise_inversion_method": "pseudo_inverse", + }, + ], ) def backend_with_readout_correction(request): return QiskitBackend(**request.param) @@ -99,7 +111,7 @@ def test_aggregate_measurements(self, backend): for batch in batches ] - measurements_set = backend.aggregregate_measurements( + measurements_set = backend.aggregate_measurements( jobs, batches, multiplicities, @@ -162,7 +174,6 @@ def test_execute_with_retries(self, backend): # Each job has a unique ID assert len(set([job.job_id() for job in jobs])) == num_jobs - @pytest.mark.skip(reason="Failing on dev, maybe problem already fixed on server?") def test_execute_with_retries_timeout(self, backend): # This test has a race condition where the IBMQ server might finish # executing the first job before the last one is submitted, causing the @@ -172,7 +183,8 @@ def test_execute_with_retries_timeout(self, backend): circuit = export_to_qiskit(self.x_cnot_circuit()) n_samples = 10 backend.retry_timeout_seconds = 0 - num_jobs = backend.device.job_limit().maximum_jobs + 1 + # need large number here as + 1 was not enough + num_jobs = backend.device.job_limit().maximum_jobs + int(10e20) # Then with pytest.raises(RuntimeError): @@ -246,23 +258,115 @@ def test_readout_correction_works_run_circuit_and_measure( # Then assert backend_with_readout_correction.readout_correction - assert backend_with_readout_correction.readout_correction_filter is not None + assert backend_with_readout_correction.readout_correction_filters is not None - def test_readout_correction_works_run_circuitset_and_measure( + def test_readout_correction_for_distributed_circuit( self, backend_with_readout_correction ): # Given - circuit = self.x_cnot_circuit() - n_samples = 10 + num_circuits = 10 + circuit = self.x_circuit() + X(5) + n_samples = 100 + + # When + measurements_set = backend_with_readout_correction.run_circuitset_and_measure( + [circuit] * num_circuits, [n_samples] * num_circuits + ) + + # Then + assert backend_with_readout_correction.readout_correction + assert ( + backend_with_readout_correction.readout_correction_filters.get(str([0, 5])) + is not None + ) + assert len(measurements_set) == num_circuits + for measurements in measurements_set: + assert len(measurements.bitstrings) == n_samples + counts = measurements.get_counts() + assert max(counts, key=counts.get) == "11" + + @pytest.mark.parametrize( + "counts, active_qubits", + [ + ({"100000000000000000001": 10}, [0, 20]), + ({"100000000000000000100": 10}, [0, 18, 20]), + ({"001000000000000000001": 10}, [2, 20]), + ], + ) + def test_subset_readout_correction( + self, counts, active_qubits, backend_with_readout_correction + ): + # Given + copied_counts = deepcopy(counts) + + # When + mitigated_counts = backend_with_readout_correction._apply_readout_correction( + copied_counts, active_qubits + ) + + # Then + assert backend_with_readout_correction.readout_correction + assert backend_with_readout_correction.readout_correction_filters.get( + str(active_qubits) + ) + assert copied_counts == pytest.approx(mitigated_counts, 10e-5) + + def test_subset_readout_correction_with_unspecified_active_qubits( + self, backend_with_readout_correction + ): + # Given + counts = {"11": 10} + + # When + mitigated_counts = backend_with_readout_correction._apply_readout_correction( + counts + ) + + # Then + assert backend_with_readout_correction.readout_correction + assert backend_with_readout_correction.readout_correction_filters.get( + str([0, 1]) + ) + assert counts == pytest.approx(mitigated_counts, 10e-5) + + def test_must_define_n_samples_for_readout_calibration_for_readout_correction( + self, backend_with_readout_correction + ): + # Given + counts, active_qubits = ({"11": 10}, None) + backend_with_readout_correction.n_samples_for_readout_calibration = None + + # When/Then + with pytest.raises(TypeError): + backend_with_readout_correction._apply_readout_correction( + counts, active_qubits + ) + + def test_subset_readout_correction_for_multiple_subsets( + self, backend_with_readout_correction + ): + # Given + counts_1, active_qubits_1 = ({"100000000000000000001": 10}, [0, 20]) + counts_2, active_qubits_2 = ({"001000000000000000001": 10}, [2, 20]) # When - backend_with_readout_correction.run_circuitset_and_measure( - [circuit] * 2, [n_samples] * 2 + mitigated_counts_1 = backend_with_readout_correction._apply_readout_correction( + counts_1, active_qubits_1 + ) + mitigated_counts_2 = backend_with_readout_correction._apply_readout_correction( + counts_2, active_qubits_2 ) # Then assert backend_with_readout_correction.readout_correction - assert backend_with_readout_correction.readout_correction_filter is not None + assert backend_with_readout_correction.readout_correction_filters.get( + str(active_qubits_1) + ) + assert backend_with_readout_correction.readout_correction_filters.get( + str(active_qubits_2) + ) + assert counts_1 == pytest.approx(mitigated_counts_1, 10e-5) + assert counts_2 == pytest.approx(mitigated_counts_2, 10e-5) def test_device_that_does_not_exist(self): # Given/When/Then diff --git a/tests/qeqiskit/conversion/circuit_conversions_test.py b/tests/qeqiskit/conversion/circuit_conversions_test.py index 839c64b..b573260 100644 --- a/tests/qeqiskit/conversion/circuit_conversions_test.py +++ b/tests/qeqiskit/conversion/circuit_conversions_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import numpy as np import pytest import qiskit diff --git a/tests/qeqiskit/conversion/openfermion_conversions_test.py b/tests/qeqiskit/conversion/openfermion_conversions_test.py index e6b8b6a..6c1e6fe 100644 --- a/tests/qeqiskit/conversion/openfermion_conversions_test.py +++ b/tests/qeqiskit/conversion/openfermion_conversions_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ ############################################################################ # Copyright 2017 Rigetti Computing, Inc. # Modified by Zapata Computing 2020 to work for qiskit's SummedOp. diff --git a/tests/qeqiskit/conversion/qiskit_expressions_test.py b/tests/qeqiskit/conversion/qiskit_expressions_test.py index 8ce4dad..ea8ae99 100644 --- a/tests/qeqiskit/conversion/qiskit_expressions_test.py +++ b/tests/qeqiskit/conversion/qiskit_expressions_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import pytest import qiskit from qeqiskit.conversions._qiskit_expressions import ( diff --git a/tests/qeqiskit/conversion/symengine_expressions_test.py b/tests/qeqiskit/conversion/symengine_expressions_test.py index e802dd0..f0243c5 100644 --- a/tests/qeqiskit/conversion/symengine_expressions_test.py +++ b/tests/qeqiskit/conversion/symengine_expressions_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ """Test cases for symengine_expressions module.""" import pytest import symengine diff --git a/tests/qeqiskit/noise/basic_test.py b/tests/qeqiskit/noise/basic_test.py index 24579f7..1bb93a8 100644 --- a/tests/qeqiskit/noise/basic_test.py +++ b/tests/qeqiskit/noise/basic_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ import os import unittest diff --git a/tests/qeqiskit/optimizer/optimizer_test.py b/tests/qeqiskit/optimizer/optimizer_test.py index d95d1ff..613b608 100644 --- a/tests/qeqiskit/optimizer/optimizer_test.py +++ b/tests/qeqiskit/optimizer/optimizer_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import numpy as np import pytest from qeqiskit.optimizer import QiskitOptimizer diff --git a/tests/qeqiskit/simulator/simulator_test.py b/tests/qeqiskit/simulator/simulator_test.py index bdff593..8ed315c 100644 --- a/tests/qeqiskit/simulator/simulator_test.py +++ b/tests/qeqiskit/simulator/simulator_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021-2022 Zapata Computing Inc. +################################################################################ import os import pytest diff --git a/tests/qeqiskit/utils_test.py b/tests/qeqiskit/utils_test.py index 5188181..a016559 100644 --- a/tests/qeqiskit/utils_test.py +++ b/tests/qeqiskit/utils_test.py @@ -1,3 +1,6 @@ +################################################################################ +# © Copyright 2021 Zapata Computing Inc. +################################################################################ import json import os import subprocess