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

[SDS-716] Quantum Volume Computation #147

Merged
merged 11 commits into from
Oct 4, 2022
8 changes: 2 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
language: python
matrix:
include:
- name: python 3.7
python: 3.7
if: branch = dev
dist: bionic
- name: python 3.8
python: 3.8
if: branch = dev
dist: xenial
dist: focal
- name: python 3.9
python: 3.9
dist: xenial
dist: focal
- name: python 3.10
python: 3.10.1
if: branch = dev
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ def get_long_description():
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'License :: OSI Approved :: Apache Software License'],
license='Apache 2.0',
packages=['quantuminspire', 'quantuminspire.qiskit', 'quantuminspire.projectq'],
install_requires=['coverage>=4.5.1', 'matplotlib>=2.1', 'pylatexenc', 'coreapi>=2.3.3', 'numpy>=1.17', 'jupyter',
'nbimporter', 'sklearn'],
install_requires=['coverage>=4.5.1', 'matplotlib>=2.1', 'pylatexenc', 'coreapi>=2.3.3', 'numpy>=1.20', 'jupyter',
'nbimporter', 'sklearn', 'qilib'],
extras_require={
'qiskit': ["qiskit>=0.20.0"],
'projectq': ["projectq>=0.4"],
Expand Down
4 changes: 2 additions & 2 deletions src/quantuminspire/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def read_account(filename: str = DEFAULT_QIRC_FILE) -> Optional[str]:
The Quantum Inspire token or None when no token is found or token is empty.
"""
try:
with open(filename, 'r') as file:
with open(filename, 'r', encoding='utf-8') as file:
accounts = json.load(file)
token: Optional[str] = accounts['token']
except (OSError, KeyError, ValueError): # file does not exist or is empty/invalid
Expand Down Expand Up @@ -126,7 +126,7 @@ def save_account(token: str, filename: str = DEFAULT_QIRC_FILE) -> None:
"""
accounts = {'token': token}
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'w') as config_file:
with open(filename, 'w', encoding='utf-8') as config_file:
json.dump(accounts, config_file, indent=2)


Expand Down
46 changes: 36 additions & 10 deletions src/quantuminspire/qiskit/backend_qx.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
from typing import Any, Dict, List, Tuple, Optional, Union, TYPE_CHECKING

import numpy as np

from qilib.utils.serialization import serializer

from qiskit.circuit import QuantumCircuit
from qiskit.compiler import assemble
from qiskit.providers import Options, BackendV1 as Backend
Expand Down Expand Up @@ -133,6 +136,7 @@ def run(self,
run_input: Union[QasmQobj, QuantumCircuit, List[QuantumCircuit]],
shots: Optional[int] = None,
memory: Optional[bool] = None,
allow_fsp: bool = True,
**run_config: Dict[str, Any]
) -> QIJob:
""" Submits a quantum job to the Quantum Inspire platform.
Expand All @@ -143,7 +147,9 @@ def run(self,
on the backend. A :class:`~qiskit.qobj.QasmQobj` object is also supported but is deprecated.
:param shots: Number of repetitions of each circuit, for sampling. Default: 1024
or ``max_shots`` from the backend configuration, whichever is smaller.
:param memory: If ``True``, per-shot measurement bitstrings are returned
:param memory: If ``True``, per-shot measurement bitstrings are returned.
:param allow_fsp: When ``False``, never submit as full_state_projection. This means: turn off possible
optimization when running a deterministic circuit on a simulator backend.
:param run_config: Extra arguments used to configure the run.

:return:
Expand Down Expand Up @@ -173,17 +179,21 @@ def run(self,
self.__validate_number_of_shots(number_of_shots)

identifier = uuid.uuid1()
project_name = 'qi-sdk-project-{}'.format(identifier)
project_name = f'qi-sdk-project-{identifier}'
project: Optional[Dict[str, Any]]
project = self.__api.create_project(project_name, number_of_shots, self.__backend)
if not allow_fsp:
# method run was called explicitly with allow_fsp = False. That is: full_state_projection is turned off.
# No need to attend the user that the execution may take longer
self.__api.show_fsp_warning(False)
try:
experiments = qobj.experiments
job = QIJob(self, str(project['id']), self.__api)
for experiment in experiments:
measurements = Measurements.from_experiment(experiment)
if Backend.configuration(self).conditional:
self.__validate_nr_of_clbits_conditional_gates(experiment)
full_state_projection = Backend.configuration(self).simulator and \
full_state_projection = allow_fsp and Backend.configuration(self).simulator and \
self.__validate_full_state_projection(experiment)
if not full_state_projection:
measurements.validate_unsupported_measurements()
Expand Down Expand Up @@ -235,13 +245,12 @@ def retrieve_job(self, job_id: str) -> QIJob:
return QIJob(self, job_id, self.__api)

def _generate_cqasm(self, experiment: QasmQobjExperiment, measurements: Measurements,
full_state_projection: bool = True) -> str:

full_state_projection: bool = False) -> str:
""" Generates the cQASM from the Qiskit experiment.

:param experiment: The experiment that contains instructions to be converted to cQASM.
:param measurements: The measurement instance containing measurement information and measurement functionality.
:param full_state_projection: When False, the experiment is not suitable for full state projection
:param full_state_projection: When True, measurement commands are not added to the resulting cQASM

:raises QiskitBackendError: If a Qiskit instruction is not in the basis gates set of Quantum Inspire backend.

Expand All @@ -255,18 +264,34 @@ def _generate_cqasm(self, experiment: QasmQobjExperiment, measurements: Measurem
with io.StringIO() as stream:
stream.write('version 1.0\n')
stream.write('# cQASM generated by QI backend for Qiskit\n')
stream.write('qubits %d\n' % number_of_qubits)
stream.write(f'qubits {number_of_qubits}\n')
for instruction in instructions:
parser.parse(stream, instruction)
return stream.getvalue()

def generate_user_data(self, experiment: QasmQobjExperiment, measurements: Measurements) -> Dict[str, Any]:
"""
Generates the user_data for this experiment. The user_data is saved together with the job and consists of
data that is necessary to process the result of the experiment correctly.

:param experiment: The experiment that contains the header information to save in the user data.
:param measurements: The measurement instance containing measurement information and measurement functionality.

:return:
A structure with user data that is needed to process the result of the experiment.
"""
return {'name': experiment.header.name, 'metadata': serializer.serialize(experiment.header.metadata),
'qubit_labels': experiment.header.qubit_labels, 'qreg_sizes': experiment.header.qreg_sizes,
'clbit_labels': experiment.header.clbit_labels, 'creg_sizes': experiment.header.creg_sizes,
'global_phase': experiment.header.global_phase, 'memory_slots': experiment.header.memory_slots,
'measurements': measurements.to_dict()}

def _submit_experiment(self, experiment: QasmQobjExperiment, number_of_shots: int,
measurements: Measurements,
project: Optional[Dict[str, Any]] = None,
full_state_projection: bool = True) -> QuantumInspireJob:
full_state_projection: bool = False) -> QuantumInspireJob:
compiled_qasm = self._generate_cqasm(experiment, measurements, full_state_projection=full_state_projection)
user_data = {'name': experiment.header.name, 'memory_slots': experiment.header.memory_slots,
'creg_sizes': experiment.header.creg_sizes, 'measurements': measurements.to_dict()}
user_data = self.generate_user_data(experiment, measurements)
quantum_inspire_job = self.__api.execute_qasm_async(compiled_qasm, backend_type=self.__backend,
number_of_shots=number_of_shots, project=project,
job_name=experiment.header.name,
Expand Down Expand Up @@ -297,6 +322,7 @@ def _get_experiment_results(self, jobs: List[Dict[str, Any]]) -> List[Experiment
f"This job was not submitted by the SDK")

user_data = json.loads(str(job.get('user_data')))
user_data['metadata'] = serializer.unserialize(user_data.get('metadata'))
measurements = Measurements.from_dict(user_data.pop('measurements'))
histogram_obj, memory_data = self.__convert_result_data(result, measurements)
full_state_histogram_obj = self.__convert_histogram(result, measurements)
Expand Down
6 changes: 3 additions & 3 deletions src/quantuminspire/qiskit/circuit_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
class CircuitToString:
""" Contains the translational elements to convert the Qiskit circuits to cQASM code."""

def __init__(self, basis_gates: List[str], measurements: Measurements, full_state_projection: bool = True) -> None:
def __init__(self, basis_gates: List[str], measurements: Measurements, full_state_projection: bool = False) -> None:
"""
:param basis_gates: List of basis gates from the configuration
:param basis_gates: List of basis gates from the configuration.
:param measurements: The measured qubits/classical bits and the number of qubits and classical bits.
:param full_state_projection: Whether or not to use full state projection.
:param full_state_projection: When full_state_projection = True, no measurement statements are added.
"""
self.basis_gates = basis_gates.copy()
if len(self.basis_gates) > 0:
Expand Down
4 changes: 3 additions & 1 deletion src/quantuminspire/qiskit/qi_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ def status(self) -> JobStatus:
running = len([job for job in jobs if job['status'] == 'RUNNING'])
completed = len([job for job in jobs if job['status'] == 'COMPLETE'])

if 0 < cancelled < number_of_jobs:
if number_of_jobs == 0:
self._status = JobStatus.INITIALIZING
elif 0 < cancelled < number_of_jobs:
self._status = JobStatus.ERROR
elif cancelled == number_of_jobs:
self._status = JobStatus.CANCELLED
Expand Down
9 changes: 4 additions & 5 deletions src/quantuminspire/qiskit/qi_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ def get_raw_result(self, field_name: str, experiment: Any = None) -> Union[List[
result_values = self.data(key)[field_name]
results_list.append(result_values)
else:
raise QiskitBackendError('Result does not contain {0} data for experiment "{1}"'.format(field_name,
key))
raise QiskitBackendError(f'Result does not contain {field_name} data for experiment "{key}"')
return results_list

def get_probabilities(self, experiment: Any = None) -> Union[Dict[str, float], List[Dict[str, float]]]:
Expand Down Expand Up @@ -116,7 +115,7 @@ def get_probabilities(self, experiment: Any = None) -> Union[Dict[str, float], L
probabilities = self.data(key)["probabilities"]
dict_list.append(postprocess.format_counts(probabilities, header))
else:
raise QiskitBackendError('No probabilities for experiment "{0}"'.format(key))
raise QiskitBackendError(f'No probabilities for experiment "{key}"')

# Return first item of dict_list if size is 1
if len(dict_list) == 1:
Expand Down Expand Up @@ -160,7 +159,7 @@ def get_probabilities_multiple_measurement(self, experiment: Any = None) -> Unio
dict_list.append(postprocess.format_counts(probabilities, header))
list_of_dict_list.append(dict_list)
else:
raise QiskitBackendError('No probabilities_multiple_measurement for experiment "{0}"'.format(key))
raise QiskitBackendError(f'No probabilities_multiple_measurement for experiment "{key}"')

# Return first item of list_dict_list if size is 1
if len(list_of_dict_list) == 1:
Expand Down Expand Up @@ -192,7 +191,7 @@ def get_calibration(self, experiment: Any = None) -> Union[Dict[str, Any], List[
calibration = self.data(key)["calibration"]
dict_list.append(calibration)
else:
raise QiskitBackendError('No calibration data for experiment "{0}"'.format(key))
raise QiskitBackendError(f'No calibration data for experiment "{key}"')

# Return first item of dict_list if size is 1
if len(dict_list) == 1:
Expand Down
2 changes: 1 addition & 1 deletion src/quantuminspire/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.0.0'
__version__ = '2.1.0'
tomrijnbeek marked this conversation as resolved.
Show resolved Hide resolved
Loading