Skip to content

Commit

Permalink
[SDS-716] Quantum Volume Computation (#147)
Browse files Browse the repository at this point in the history
* [SDS-716] Issue quantum volume computation
* build environment update
* setup.py newer numpy
* version upgrade and setup dependency added
* remove travis build for 3.7
* added some comments and small non-functional change
  • Loading branch information
QFer authored Oct 4, 2022
1 parent 61b0821 commit 9e401a9
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 64 deletions.
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'
Loading

0 comments on commit 9e401a9

Please sign in to comment.