Esempio n. 1
0
    def __init__(self):
        """
        0  -  1  -  2  -  3  -  4  -  5  -  6

              |     |     |     |     |     |

              13 -  12  - 11 -  10 -  9  -  8  -   7
        """
        cmap = [[1, 0], [1, 2], [2, 3], [4, 3], [4, 10], [5, 4], [5, 6],
                [5, 9], [6, 8], [7, 8], [9, 8], [9, 10], [11, 3], [11, 10],
                [11, 12], [12, 2], [13, 1], [13, 12]]

        configuration = BackendConfiguration(
            backend_name='fake_melbourne',
            backend_version='0.0.0',
            n_qubits=14,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=cmap,
        )

        super().__init__(configuration)
Esempio n. 2
0
    def __init__(self):
        """
        1  -  2  -  3  -  4  -  5  -  6  -  7   -  8

        |     |     |     |     |     |     |      |

        0  -  15 -  14 -  13 -  12 -  11 -  10  -  9
        """
        cmap = [[1, 0], [1, 2], [2, 3], [3, 4], [3, 14], [5, 4], [6,
                                                                  5], [6, 7],
                [6, 11], [7, 10], [8, 7], [9, 8], [9, 10], [11, 10], [12, 5],
                [12, 11], [12, 13], [13, 4], [13, 14], [15, 0], [15, 2],
                [15, 14]]

        configuration = BackendConfiguration(
            backend_name='fake_rueschlikon',
            backend_version='0.0.0',
            n_qubits=16,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=cmap,
        )

        super().__init__(configuration)
Esempio n. 3
0
    def __init__(self):
        """
            1
          / |
        0 - 2 - 3
            | /
            4
        """
        cmap = [[1, 0], [2, 0], [2, 1], [3, 2], [3, 4], [4, 2]]

        configuration = BackendConfiguration(
            backend_name='fake_tenerife',
            backend_version='0.0.0',
            n_qubits=5,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=cmap,
        )

        super().__init__(configuration)
Esempio n. 4
0
    def __init__(self,
                 n_qubits,
                 coupling_map,
                 basis_gates=None,
                 name='AbstractBackend'):
        """
        Args:
            n_qubits (int): Number of qubits.
            coupling_map (list): Coupling map.
            basis_gates (list): Basis gates.
            name (str): Name of backend instance.
        """
        if basis_gates is None:
            basis_gates = ['u1', 'u2', 'u3', 'cx', 'id']

        configuration = BackendConfiguration(
            backend_name=name,
            backend_version='0.0.0',
            n_qubits=n_qubits,
            basis_gates=basis_gates,
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=8192,
            gates=[GateConfig(name='None', parameters=[], qasm_def='None')],
            coupling_map=coupling_map)
        super().__init__(configuration)
Esempio n. 5
0
    def __init__(self):
        configuration = BackendConfiguration(
            backend_name='fake_openpulse_2q',
            backend_version='0.0.0',
            n_qubits=2,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=True,
            open_pulse=True,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=[[1, 0]],
            n_registers=2,
            n_uchannels=2,
            u_channel_lo=[[{
                "q": 0,
                "scale": [1, 0]
            }], [{
                "q": 0,
                "scale": [-1, 0]
            }, {
                "q": 1,
                "scale": [1, 0]
            }]],
            meas_level=[1, 2],
            qubit_lo_range=[[4.5, 5.5], [4.5, 5.5]],
            meas_lo_range=[[6.0, 7.0], [6.0, 7.0]],
            dt=1.3333,
            dtm=10.5,
            rep_times=[100, 250, 500, 1000],
            meas_map=[[0], [1, 2]],
            channel_bandwidth=[[-0.2, 0.4], [-0.3, 0.3], [-0.3, 0.3],
                               [-0.02, 0.02], [-0.02, 0.02], [-0.02, 0.02]],
            meas_kernels=['kernel1'],
            discriminators=['max_1Q_fidelity'],
            acquisition_latency=[[100, 100], [100, 100]],
            conditional_latency=[[100, 1000], [1000, 100], [100, 1000],
                                 [1000, 100], [100, 1000], [1000, 100]])

        self._defaults = PulseDefaults(qubit_freq_est=[4.9, 5.0],
                                       meas_freq_est=[6.5, 6.6],
                                       buffer=10,
                                       pulse_library=[],
                                       cmd_def=[])

        super().__init__(configuration)
Esempio n. 6
0
    def configuration(self):
        qx4_cmap = [[1, 0], [2, 0], [2, 1], [3, 2], [3, 4], [4, 2]]

        return BackendConfiguration(
            backend_name='fake_qx4',
            backend_version='0.0.0',
            n_qubits=5,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=qx4_cmap,
        )
Esempio n. 7
0
    def __init__(self):
        """
        0  =  1   =  2   =  3     4

        ||    ||    ||     ||  X  ||

        5  =  6   =  7   =  8  =  9

        || X  ||    ||   X  ||

        10 =  11  =  12  =  13 =  14

        ||    ||  X         || X  ||

        15 =  16  =  17     18    19
        """
        cmap = [[0, 1], [0, 5], [1, 0], [1, 2], [1, 6], [2, 1], [2, 3], [2, 6],
                [3, 2], [3, 8], [3, 9], [4, 8], [4, 9], [5, 0],
                [5, 6], [5, 10], [5, 11], [6, 1], [6, 2], [6, 5], [6, 7],
                [6, 10], [6, 11], [7, 1], [7, 6], [7, 8], [7, 12], [7, 13],
                [8, 3], [8, 4], [8, 7], [8, 9], [8, 12], [8, 13], [9,
                                                                   3], [9, 4],
                [9, 8], [10, 5], [10, 6], [10, 11], [10, 15], [11, 5], [11, 6],
                [11, 10], [11, 12], [11, 16], [11, 17], [12, 7], [12, 8],
                [12, 11], [12, 13], [12, 16], [13, 7], [13, 8], [13, 12],
                [13, 14], [13, 18], [13, 19], [14, 13], [14, 18], [14, 19],
                [15, 10], [15, 16], [16, 11], [16, 12], [16, 15], [16, 17],
                [17, 11], [17, 16], [18, 13], [18, 14], [19, 13], [19, 14]]

        configuration = BackendConfiguration(
            backend_name='fake_tokyo',
            backend_version='0.0.0',
            n_qubits=16,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=cmap,
        )

        super().__init__(configuration)
Esempio n. 8
0
    def __init__(self, time_alive=10):
        configuration = BackendConfiguration(
            backend_name='fake_failure_qasm_simulator',
            backend_version='0.0.0',
            n_qubits=5,
            basis_gates=[
                'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's',
                'sdg', 't', 'tdg', 'ccx', 'swap', 'snapshot', 'unitary'
            ],
            simulator=True,
            local=True,
            conditional=True,
            open_pulse=False,
            memory=True,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')])

        super().__init__(configuration, time_alive=time_alive)
Esempio n. 9
0
 def configuration(self):
     """Return a make up configuration for a fake device."""
     qx5_cmap = [[1, 0], [1, 2], [2, 3], [3, 4], [3, 14], [5, 4], [6, 5],
                 [6, 7], [6, 11], [7, 10], [8, 7], [9, 8], [9, 10],
                 [11, 10], [12, 5], [12, 11], [12, 13], [13, 4], [13, 14],
                 [15, 0], [15, 2], [15, 14]]
     return BackendConfiguration(
         backend_name='fake',
         backend_version='0.0.0',
         n_qubits=16,
         basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
         simulator=False,
         local=True,
         conditional=False,
         open_pulse=False,
         memory=False,
         max_shots=65536,
         gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
         coupling_map=qx5_cmap,
     )
Esempio n. 10
0
 def test_backend_default_configuration(self):
     simulator = QuantumInspireBackend(Mock(), Mock())
     configuration = simulator.configuration()
     expected_configuration = QasmBackendConfiguration(
         backend_name='qi_simulator',
         backend_version=quantum_inspire_version,
         n_qubits=26,
         basis_gates=['x', 'y', 'z', 'h', 'rx', 'ry', 'rz', 's', 'sdg', 't', 'tdg', 'cx', 'ccx', 'u1', 'u2', 'u3',
                      'id', 'swap', 'cz', 'snapshot'],
         gates=[GateConfig(name='NotUsed', parameters=['NaN'], qasm_def='NaN')],
         conditional=True,
         simulator=True,
         local=False,
         memory=True,
         open_pulse=False,
         max_shots=1024,
         max_experiments=1,
         coupling_map=None
     )
     self.assertDictEqual(configuration.to_dict(), expected_configuration.to_dict())
Esempio n. 11
0
class QuantumInspireBackend(BaseBackend):  # type: ignore
    DEFAULT_CONFIGURATION = QasmBackendConfiguration(
        backend_name='qi_simulator',
        backend_version=quantum_inspire_version,
        n_qubits=26,
        basis_gates=[
            'x', 'y', 'z', 'h', 'rx', 'ry', 'rz', 's', 'sdg', 't', 'tdg', 'cx',
            'ccx', 'u1', 'u2', 'u3', 'id', 'swap', 'cz', 'snapshot'
        ],
        gates=[GateConfig(name='NotUsed', parameters=['NaN'], qasm_def='NaN')],
        local=False,
        simulator=True,
        conditional=True,
        open_pulse=False,
        memory=True,
        max_shots=1024,
        max_experiments=1,
        coupling_map=None)

    def __init__(
            self,
            api: QuantumInspireAPI,
            provider: Any,
            configuration: Optional[QasmBackendConfiguration] = None) -> None:
        """ Python implementation of a quantum simulator using Quantum Inspire API.

        Args:
            api: The interface instance to the Quantum Inspire API.
            provider: Provider for this backend.
            configuration: The configuration of the quantum inspire backend. The
                configuration must implement the fields given by the QiSimulatorPy.DEFAULT_CONFIGURATION. All
                configuration fields are listed in the table below. The table rows with an asterisk specify fields which
                can have a custom value and are allowed to be changed according to the description column.

                | key                        | description
                |----------------------------|-------------------------------------------------------------------------
                | backend_name (str)*        | The name of the quantum inspire backend. The API can list the name of
                |                            | each available backend using the function api.list_backend_types(). One
                |                            | of the listed names must be used.
                | backend_version (str)      | Backend version in the form X.Y.Z.
                | n_qubits (int)             | Number of qubits.
                | basis_gates (list(str))    | A list of basis gates to compile to.
                | gates (GateConfig)         | List of basis gates on the backend. Not used.
                | local (bool)               | Indicates whether the system is running locally or remotely.
                | simulator (bool)           | Specifies whether the backend is a simulator or a quantum system.
                | conditional (bool)         | Backend supports conditional operations.
                | open_pulse (bool)          | Backend supports open pulse. False.
                | memory (bool)              | Backend supports memory. True.
                | max_shots (int)            | Maximum number of shots supported.
                | max_experiments (int)      | Optional: Maximum number of experiments (circuits) per job.
                | coupling_map (list(tuple)) | Define the edges.
        """
        super().__init__(
            configuration=(configuration
                           or QuantumInspireBackend.DEFAULT_CONFIGURATION),
            provider=provider)
        self.__backend: Dict[str,
                             Any] = api.get_backend_type_by_name(self.name())
        self.__api: QuantumInspireAPI = api

    @property
    def backend_name(self) -> str:
        return self.name()  # type: ignore

    def run(self, qobj: QasmQobj) -> QIJob:
        """ Submits a quantum job to the Quantum Inspire platform.

        Args:
            qobj: The quantum job with the Qiskit algorithm and quantum inspire backend.

        Returns:
            A job that has been submitted.
        """
        self.__validate_number_of_shots(qobj)
        number_of_shots = qobj.config.shots

        identifier = uuid.uuid1()
        project_name = 'qi-sdk-project-{}'.format(identifier)
        project = self.__api.create_project(project_name, number_of_shots,
                                            self.__backend)
        experiments = qobj.experiments
        job = QIJob(self, str(project['id']), self.__api)
        for experiment in experiments:
            self.__validate_number_of_clbits(experiment)
            full_state_projection = self.__validate_full_state_projection(
                experiment)
            if not full_state_projection:
                QuantumInspireBackend.__validate_unsupported_measurements(
                    experiment)
            self._submit_experiment(
                experiment,
                number_of_shots,
                project=project,
                full_state_projection=full_state_projection)

        job.experiments = experiments
        return job

    def retrieve_job(self, job_id: str) -> QIJob:
        """ Retrieve a specified job by its job_id.

        Args:
            job_id: The job id.

        Returns:
            The job that has been retrieved.

        Raises:
            QisKitBackendError: If job not found or error occurs during retrieval of the job.
        """
        try:
            self.__api.get_project(int(job_id))
        except (ErrorMessage, ValueError):
            raise QisKitBackendError(
                "Could not retrieve job with job_id '{}' ".format(job_id))
        return QIJob(self, job_id, self.__api)

    @staticmethod
    def _generate_cqasm(experiment: QasmQobjExperiment,
                        full_state_projection: bool = True) -> str:
        """ Generates the cQASM from the Qiskit experiment.

        Args:
            experiment: The experiment that contains instructions to be converted to cQASM.
            full_state_projection: When False, the experiment is not suitable for full state projection

        Returns:
            The cQASM code that can be sent to the Quantum Inspire API.
        """
        parser = CircuitToString(full_state_projection)
        number_of_qubits = experiment.header.n_qubits
        instructions = experiment.instructions
        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)
            for instruction in instructions:
                parser.parse(stream, instruction)
            return stream.getvalue()

    def _submit_experiment(
            self,
            experiment: QasmQobjExperiment,
            number_of_shots: int,
            project: Optional[Dict[str, Any]] = None,
            full_state_projection: bool = True) -> QuantumInspireJob:
        compiled_qasm = self._generate_cqasm(
            experiment, full_state_projection=full_state_projection)
        measurements = self._collect_measurements(experiment)
        user_data = {
            'name': experiment.header.name,
            'memory_slots': experiment.header.memory_slots,
            'creg_sizes': experiment.header.creg_sizes,
            'measurements': measurements
        }
        job_id = self.__api.execute_qasm_async(
            compiled_qasm,
            backend_type=self.__backend,
            number_of_shots=number_of_shots,
            project=project,
            job_name=experiment.header.name,
            user_data=json.dumps(user_data),
            full_state_projection=full_state_projection)
        return job_id

    def get_experiment_results(self, qi_job: QIJob) -> List[ExperimentResult]:
        """ Get results from experiments from the Quantum-inspire platform.

        Args:
            qi_job: A job that has already been submitted and which execution is completed.

        Raises:
            QisKitBackendError: If an error occurred during execution by the backend.

        Returns:
            A list of experiment results; containing the data, execution time, status, etc.
        """
        jobs = self.__api.get_jobs_from_project(int(qi_job.job_id()))
        results = [self.__api.get_result_from_job(job['id']) for job in jobs]
        experiment_results = []
        for result, job in zip(results, jobs):
            if not result.get('histogram', {}):
                raise QisKitBackendError(
                    'Result from backend contains no histogram data!\n{}'.
                    format(result.get('raw_text')))

            user_data = json.loads(str(job.get('user_data')))
            measurements = user_data.pop('measurements')
            histogram_obj, memory_data = self.__convert_result_data(
                result, measurements)
            full_state_histogram_obj = self.__convert_histogram(
                result, measurements)
            experiment_result_data = ExperimentResultData(counts=histogram_obj,
                                                          memory=memory_data)
            experiment_result_data.probabilities = full_state_histogram_obj
            header = QobjExperimentHeader.from_dict(user_data)
            experiment_result_dictionary = {
                'name': job.get('name'),
                'seed': 42,
                'shots': job.get('number_of_shots'),
                'data': experiment_result_data,
                'status': 'DONE',
                'success': True,
                'time_taken': result.get('execution_time_in_seconds'),
                'header': header
            }
            experiment_results.append(
                ExperimentResult(**experiment_result_dictionary))
        return experiment_results

    def __validate_number_of_shots(self, job: QasmQobj) -> None:
        """ Checks whether the number of shots has a valid value.

        Args:
            job: The quantum job with the Qiskit algorithm and quantum inspire backend.

        Raises:
            QisKitBackendError: When the value is not correct.
        """
        number_of_shots = job.config.shots
        if number_of_shots < 1 or number_of_shots > self.__backend[
                'max_number_of_shots']:
            raise QisKitBackendError(
                'Invalid shots (number_of_shots={})'.format(number_of_shots))

    def __validate_number_of_clbits(self,
                                    experiment: QasmQobjExperiment) -> None:
        """ Checks whether the number of classical bits has a value cQASM can support.

            1. When number of classical bits is less than 1 an error is raised.
            2. When binary controlled gates are used and the number of classical registers > number of classical
            registers an error is raised.
                When using binary controlled gates in Qiskit, we can have something like:
                q = QuantumRegister(2)
                c = ClassicalRegister(4)
                circuit = QuantumCircuit(q, c)
                circuit.h(q[0]).c_if(c, 15)

                Because cQASM has the same number of classical registers as qubits (2 in this case),
                this circuit cannot be translated to valid cQASM.

        Args:
            experiment: The experiment with gate operations and header.

        Raises:
            QisKitBackendError: When the value is not correct.
        """
        number_of_clbits = experiment.header.memory_slots
        if number_of_clbits < 1:
            raise QisKitBackendError(
                "Invalid amount of classical bits ({})!".format(
                    number_of_clbits))

        if BaseBackend.configuration(self).conditional:
            number_of_qubits = experiment.header.n_qubits
            if number_of_clbits > number_of_qubits:
                # no problem when there are no conditional gate operations
                for instruction in experiment.instructions:
                    if hasattr(instruction, 'conditional'):
                        raise QisKitBackendError(
                            "Number of classical bits must be less than or equal to the"
                            " number of qubits when using conditional gate operations"
                        )

    @staticmethod
    def __validate_full_state_projection(
            experiment: QasmQobjExperiment) -> bool:
        """ FSP (Full State Projection) can be used when no measurements are found in the circuit or when no
            other gates are found after measurements.

        Args:
            experiment: The experiment with gate operations and header.

        Returns:
            True when FSP can be used, otherwise False.
        """
        fsp = True
        measurement_found = False
        for instruction in experiment.instructions:
            if instruction.name == 'measure':
                measurement_found = True
            elif measurement_found:
                fsp = False
        return fsp

    @staticmethod
    def __validate_unsupported_measurements(
            experiment: QasmQobjExperiment) -> None:
        """ When using non-FSP (not full state projection) certain measurements cannot be handled correctly because
            cQASM isn't as flexible as Qiskit in measuring to specific classical bits.
            Therefore some Qiskit constructions are not supported in QI:

            1. When a quantum register is measured to different classical registers
            2. When a classical register is used for the measurement of more than one quantum register

        Args:
            experiment: The experiment with gate operations and header.

        Raises:
            QisKitBackendError: When the circuit contains an invalid non-FSP measurement
        """
        measurements: List[List[int]] = []
        for instruction in experiment.instructions:
            if instruction.name == 'measure':
                for q, m in measurements:
                    if q == instruction.qubits[
                            0] and m != instruction.memory[0]:
                        raise QisKitBackendError(
                            'Measurement of qubit {} to different classical registers '
                            'is not supported'.format(q))
                    if q != instruction.qubits[0] and m == instruction.memory[
                            0]:
                        raise QisKitBackendError(
                            'Measurement of different qubits to the same classical register {0} '
                            'is not supported'.format(m))
                measurements.append(
                    [instruction.qubits[0], instruction.memory[0]])

    @staticmethod
    def _collect_measurements(
            experiment: QasmQobjExperiment) -> Dict[str, Any]:
        """ Determines the measured qubits and classical bits. The full-state measured
            qubits is returned when no measurements are present in the compiled circuit.

        Args:
            experiment: The experiment with gate operations and header.

        Returns:
            The dict contains measurements, which is a list of lists, for each measurement the list contains
            a list of [qubit_index, classical_bit_index], which represents the measurement of a qubit to a
            classical bit, and the second field in the dict is the number of classical bits (int).
        """
        header = experiment.header
        number_of_qubits = header.n_qubits
        number_of_clbits = header.memory_slots

        operations = experiment.instructions
        measurements = [[
            number_of_qubits - 1 - m.qubits[0],
            number_of_clbits - 1 - m.memory[0]
        ] for m in operations if m.name == 'measure']
        if not measurements:
            measurements = [[index, index]
                            for index in range(number_of_qubits)]
        return {
            'measurements': measurements,
            'number_of_clbits': number_of_clbits
        }

    @staticmethod
    def __qubit_to_classical_hex(qubit_register: str, measurements: Dict[str,
                                                                         Any],
                                 number_of_qubits: int) -> str:
        """ This function converts the qubit register data to the hexadecimal representation of the classical state.

        Args:
            qubit_register: The measured value of the qubits represented as int.
            measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).
            number_of_qubits: Number of qubits used in the algorithm.

        Returns:
            The hexadecimal value of the classical state.
        """
        qubit_state = ('{0:0{1}b}'.format(int(qubit_register),
                                          number_of_qubits))
        classical_state = ['0'] * measurements['number_of_clbits']
        for q, c in measurements['measurements']:
            classical_state[c] = qubit_state[q]
        classical_state_str = ''.join(classical_state)
        classical_state_hex = hex(int(classical_state_str, 2))
        return classical_state_hex

    @staticmethod
    def __convert_histogram(result: Dict[str, Any],
                            measurements: Dict[str, Any]) -> Dict[str, float]:
        """ The quantum inspire backend always uses full state projection. The SDK user
            can measure not all qubits and change the combined classical bits. This function
            converts the result to a histogram output that represents the probabilities
            measured with the classical bits.

        Args:
            result: The result output from the quantum inspire backend with full-
                    state projection histogram output.
            measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).

        Returns:
            The resulting full state histogram with probabilities.
        """
        output_histogram_probabilities: Dict[str,
                                             float] = defaultdict(lambda: 0)
        number_of_qubits = result['number_of_qubits']
        state_probability: Dict[str, float] = result['histogram']
        for qubit_register, probability in state_probability.items():
            classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(
                qubit_register, measurements, number_of_qubits)
            output_histogram_probabilities[classical_state_hex] += probability

        sorted_histogram_probabilities: List[Tuple[str, float]] = sorted(
            output_histogram_probabilities.items(),
            key=lambda kv: int(kv[0], 16))
        return dict(sorted_histogram_probabilities)

    def __convert_result_data(
            self, result: Dict[str, Any],
            measurements: Dict[str, Any]) -> Tuple[Dict[str, int], List[str]]:
        """ The quantum inspire backend returns the single shot values as raw data. This function
            converts this list of single shot values to hexadecimal memory data according the Qiskit spec.
            From this memory data the counts histogram is constructed by counting the single shot values.

        Note:
            When shots = 1, the backend returns an empty list as raw_data. This is a special case. In this case the
            resulting memory data consists of 1 value and the count histogram consists of 1 instance of this value.
            To determine this value a random float is generated in the range [0, 1). With this random number the
            value from this probabilities histogram is taken where the added probabilities is greater this random
            number.
            Example: probability histogram is {[0x0, 0.2], [0x3, 0.4], [0x5, 0.1], [0x6, 0.3]}.
            When random is in the range [0, 0.2) the first value of the probability histogram is taken (0x0).
            When random is in the range [0.2, 0.6) the second value of the probability histogram is taken (0x3).
            When random is in the range [0.6, 0.7) the third value of the probability histogram is taken (0x5).
            When random is in the range [0.7, 1) the last value of the probability histogram is taken (0x6).

        Args:
            result: The result output from the quantum inspire backend with full-
                    state projection histogram output.
            measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).

        Returns:
            The result consists of two formats for the result. The first result is the histogram with count data,
            the second result is a list with converted hexadecimal memory values for each shot.
        """
        memory_data = []
        histogram_data: Dict[str, int] = defaultdict(lambda: 0)
        number_of_qubits: int = result['number_of_qubits']
        raw_data = self.__api.get_raw_data_from_result(result['id'])
        if raw_data:
            for raw_qubit_register in raw_data:
                classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(
                    str(raw_qubit_register), measurements, number_of_qubits)
                memory_data.append(classical_state_hex)
            histogram_data = {
                elem: count
                for elem, count in Counter(memory_data).items()
            }
        else:
            state_probabilities = result['histogram']
            random_probability = np.random.rand()
            sum_probability = 0.0
            for qubit_register, probability in state_probabilities.items():
                sum_probability += probability
                if random_probability < sum_probability:
                    classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(
                        qubit_register, measurements, number_of_qubits)
                    memory_data.append(classical_state_hex)
                    histogram_data[classical_state_hex] = 1
                    break

        sorted_histogram_data: List[Tuple[str, int]] = sorted(
            histogram_data.items(), key=lambda kv: int(kv[0], 16))
        histogram_obj = OrderedDict(sorted_histogram_data)
        return dict(histogram_obj), memory_data
Esempio n. 12
0
    "ry",
    "rz",
    "cx",
    "cy",
    "cz",
    "ch",
    "crz",
    "cu1",
    "cu3",
    "swap",
    "ccx",
    "cswap",
    "r",
]

_QLM_GATES = [GateConfig(name="FOO", parameters=[], qasm_def="BAR")]

_QLM_PARAMS = {
    "backend_name": "QiskitConnector",  # Name of the back end
    "backend_version": "0.0.1",  # Version of the back end
    "n_qubits": 100,  # Nb qbits
    "basis_gates": _QLM_GATE_NAMES,  # We accept all the gates of Qiskit
    "gates": _QLM_GATES,  # They don't even use it for their simulators, so...
    "local": True,  # Its a local backend
    "simulator": True,  # Its a simulator. Is it though?
    "conditional": True,  # We support conditionals
    "open_pulse": False,  # We do not support open Pulse
    "memory": False,  # We do not support Memory (wth?)
    "max_shots": 4096,
    "coupling_map": None,
}  # Max shots is 4096 (required :/)
Esempio n. 13
0
class QuantumInspireBackend(Backend):  # type: ignore
    DEFAULT_CONFIGURATION = QasmBackendConfiguration(
        backend_name='qi_simulator',
        backend_version=quantum_inspire_version,
        n_qubits=26,
        basis_gates=['x', 'y', 'z', 'h', 'rx', 'ry', 'rz', 's', 'sdg', 't', 'tdg', 'cx', 'ccx', 'u1', 'u2', 'u3', 'id',
                     'swap', 'cz', 'snapshot'],
        gates=[GateConfig(name='NotUsed', parameters=['NaN'], qasm_def='NaN')],
        local=False,
        simulator=True,
        conditional=True,
        open_pulse=False,
        memory=True,
        max_shots=1024,
        max_experiments=1,
        coupling_map=None
    )
    qobj_warning_issued = False

    def __init__(self, api: QuantumInspireAPI, provider: Any,
                 configuration: Optional[QasmBackendConfiguration] = None) -> None:
        """ Python implementation of a quantum simulator using Quantum Inspire API.

        :param api: The interface instance to the Quantum Inspire API.
        :param provider: Provider for this backend.
        :param configuration: The configuration of the Quantum Inspire backend. The
                configuration must implement the fields given by the QiSimulatorPy.DEFAULT_CONFIGURATION. All
                configuration fields are listed in the table below. The table rows with an asterisk specify fields which
                can have a custom value and are allowed to be changed according to the description column.

                =================== ============= =====================================================================
                Key                 Type          Description
                =================== ============= =====================================================================
                ``backend_name`` *  str           The name of the Quantum Inspire backend. The API can list the name of
                                                  each available backend using the function api.list_backend_types().
                                                  One of the listed names must be used.
                ``backend_version`` str           Backend version in the form X.Y.Z.
                ``n_qubits``        int           Number of qubits.
                ``basis_gates``     list[str]     A list of basis gates to compile to.
                ``gates``           GateConfig    List of basis gates on the backend. Not used.
                ``local``           bool          Indicates whether the system is running locally or remotely.
                ``simulator``       bool          Specifies whether the backend is a simulator or a quantum system.
                ``conditional``     bool          Backend supports conditional operations.
                ``open_pulse``      bool          Backend supports open pulse. False.
                ``memory``          bool          Backend supports memory. True.
                ``max_shots``       int           Maximum number of shots supported.
                ``max_experiments`` int           Optional: Maximum number of experiments (circuits) per job.
                ``coupling_map``    list[list]    Define the edges.
                =================== ============= =====================================================================
        """
        super().__init__(configuration=(configuration or
                                        QuantumInspireBackend.DEFAULT_CONFIGURATION),
                         provider=provider)
        self.__backend: Dict[str, Any] = api.get_backend_type_by_name(self.name())
        self.__api: QuantumInspireAPI = api

    @classmethod
    def _default_options(cls) -> Options:
        """ Returns default runtime options. Only the options that are relevant to Quantum Inspire backends are added.
        """
        return Options(shots=1024, memory=True)

    def _get_run_config(self, **kwargs: Any) -> Dict[str, Any]:
        """ Return the consolidated runtime configuration. Run arguments overwrite the values of the default runtime
        options. Run arguments (not None) that are not defined as options are added to the runtime configuration.

        :param kwargs: The runtime arguments (arguments given with the run method).

        :return:
            A dictionary of runtime arguments for the run.
        """
        run_config_dict: Dict[str, Any] = copy.copy(self.options.__dict__)
        for key, val in kwargs.items():
            if val is not None:
                run_config_dict[key] = val
        return run_config_dict

    @property
    def backend_name(self) -> str:
        return self.name()  # type: ignore

    def run(self,
            circuits: Union[QasmQobj, QuantumCircuit, List[QuantumCircuit]],
            shots: Optional[int] = None,
            memory: Optional[bool] = None,
            **run_config: Dict[str, Any]
            ) -> QIJob:
        """ Submits a quantum job to the Quantum Inspire platform.

        The execution is asynchronous, and a handle to a job instance is returned.

        :param circuits: An individual or a list of :class:`~qiskit.circuits.QuantumCircuit` objects to run
                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 run_config: Extra arguments used to configure the run.

        :return:
            A job that has been submitted.

        Raises:
            ApiError: If an unexpected error occurred while submitting the job to the backend.
            QiskitBackendError: If the circuit is invalid for the Quantum Inspire backend.
        """
        run_config_dict = self._get_run_config(
            shots=shots,
            memory=memory,
            **run_config)

        if isinstance(circuits, QasmQobj):
            if not self.qobj_warning_issued:
                warnings.warn("Passing a Qobj to QuantumInspireBackend.run is deprecated and will "
                              "be removed in a future release. Please pass in circuits "
                              "instead.", DeprecationWarning,
                              stacklevel=3)
                self.qobj_warning_issued = True
            qobj = circuits
        else:
            qobj = assemble(circuits, self, **run_config_dict)

        number_of_shots = qobj.config.shots
        self.__validate_number_of_shots(number_of_shots)

        identifier = uuid.uuid1()
        project_name = 'qi-sdk-project-{}'.format(identifier)
        project: Optional[Dict[str, Any]]
        project = self.__api.create_project(project_name, number_of_shots, self.__backend)
        try:
            experiments = qobj.experiments
            job = QIJob(self, str(project['id']), self.__api)
            for experiment in experiments:
                self.__validate_number_of_clbits(experiment)
                full_state_projection = Backend.configuration(self).simulator and \
                    self.__validate_full_state_projection(experiment)
                if not full_state_projection:
                    QuantumInspireBackend.__validate_unsupported_measurements(experiment)
                job_for_experiment = self._submit_experiment(experiment, number_of_shots, project=project,
                                                             full_state_projection=full_state_projection)
                job.add_job(job_for_experiment)
                if project is not None and job_for_experiment.get_project_identifier() != project['id']:
                    self.__api.delete_project(int(project['id']))
                    project = None
                    job.set_job_id(str(job_for_experiment.get_project_identifier()))
        except (ApiError, QiskitBackendError) as error:
            # delete the empty project
            if project is not None:
                self.__api.delete_project(int(project['id']))
            raise error

        job.experiments = experiments
        return job

    def status(self) -> BackendStatus:
        """ Return the backend status. Pending jobs is always 0. This information is currently not known.

        Returns:
            BackendStatus: the status of the backend. Pending jobs is always 0.
        """
        backend: Dict[str, Any] = self.__api.get_backend_type_by_name(self.name())
        return BackendStatus(
            backend_name=self.name(),
            backend_version=quantum_inspire_version,
            operational=backend["status"] != "OFFLINE",
            pending_jobs=0,
            status_msg=backend["status_message"],
        )

    def retrieve_job(self, job_id: str) -> QIJob:
        """ Retrieve a specified job by its job_id.

        :param job_id: The job id.

        :return:
            The job that has been retrieved.

        :raises QiskitBackendError: If job not found or error occurs during retrieval of the job.
        """
        try:
            self.__api.get_project(int(job_id))
        except (ApiError, ValueError):
            raise QiskitBackendError("Could not retrieve job with job_id '{}' ".format(job_id))
        return QIJob(self, job_id, self.__api)

    @staticmethod
    def _generate_cqasm(experiment: QasmQobjExperiment, full_state_projection: bool = True) -> str:
        """ Generates the cQASM from the Qiskit experiment.

        :param experiment: The experiment that contains instructions to be converted to cQASM.
        :param full_state_projection: When False, the experiment is not suitable for full state projection

        :return:
            The cQASM code that can be sent to the Quantum Inspire API.
        """
        parser = CircuitToString(full_state_projection)
        number_of_qubits = experiment.header.n_qubits
        instructions = experiment.instructions
        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)
            for instruction in instructions:
                parser.parse(stream, instruction)
            return stream.getvalue()

    def _submit_experiment(self, experiment: QasmQobjExperiment, number_of_shots: int,
                           project: Optional[Dict[str, Any]] = None,
                           full_state_projection: bool = True) -> QuantumInspireJob:
        compiled_qasm = self._generate_cqasm(experiment, full_state_projection=full_state_projection)
        measurements = self._collect_measurements(experiment)
        user_data = {'name': experiment.header.name, 'memory_slots': experiment.header.memory_slots,
                     'creg_sizes': experiment.header.creg_sizes, 'measurements': 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,
                                                            user_data=json.dumps(user_data),
                                                            full_state_projection=full_state_projection)
        return quantum_inspire_job

    def _get_experiment_results(self, jobs: List[Dict[str, Any]]) -> List[ExperimentResult]:
        """ Get results from experiments from the Quantum Inspire platform for one or more jobs.

        :param jobs: A list of jobs

        :raises QiskitBackendError: If an error occurred while executing the job on the Quantum Inspire backend.

        :return:
            A list of experiment results; containing the data, execution time, status, etc. for the list of jobs.
        """
        results = [self.__api.get_result_from_job(job['id']) for job in jobs]
        experiment_results = []
        for result, job in zip(results, jobs):
            if not result.get('histogram', {}):
                raise QiskitBackendError(
                    'Result from backend contains no histogram data!\n{}'.format(result.get('raw_text')))

            user_data = json.loads(str(job.get('user_data')))
            measurements = user_data.pop('measurements')
            histogram_obj, memory_data = self.__convert_result_data(result, measurements)
            full_state_histogram_obj = self.__convert_histogram(result, measurements)
            calibration = self.__api.get_calibration_from_result(result['id'])
            experiment_result_data = ExperimentResultData(counts=histogram_obj,
                                                          memory=memory_data,
                                                          probabilities=full_state_histogram_obj,
                                                          calibration=calibration)
            header = QobjExperimentHeader.from_dict(user_data)
            experiment_result_dictionary = {'name': job.get('name'), 'seed': 42, 'shots': job.get('number_of_shots'),
                                            'data': experiment_result_data, 'status': 'DONE', 'success': True,
                                            'time_taken': result.get('execution_time_in_seconds'), 'header': header}
            experiment_results.append(ExperimentResult(**experiment_result_dictionary))
        return experiment_results

    def get_experiment_results_from_latest_run(self, qi_job: QIJob) -> List[ExperimentResult]:
        """
        :param qi_job: A job that has already been submitted and which execution is completed.

        :return:
            A list of experiment results; containing the data, execution time, status, etc. for the experiments in the
            latest job run in the Quantum Inspire project.
        """
        jobs = qi_job.get_jobs()
        return self._get_experiment_results(jobs)

    def get_experiment_results_from_all_jobs(self, qi_job: QIJob) -> List[ExperimentResult]:
        """
        :param qi_job: A job that has already been submitted and which execution is completed.

        :return:
            A list of experiment results; containing the data, execution time, status, etc. for all the experiments in
            all the job runs of the Quantum Inspire project.
        """
        jobs = self.__api.get_jobs_from_project(int(qi_job.job_id()))
        return self._get_experiment_results(jobs)

    def __validate_number_of_shots(self, number_of_shots: int) -> None:
        """ Checks whether the number of shots has a valid value.

        :param job: The quantum job with the Qiskit algorithm and Quantum Inspire backend.

        :raises QiskitBackendError: When the value is not correct.
        """
        if number_of_shots < 1 or number_of_shots > self.__backend['max_number_of_shots']:
            raise QiskitBackendError('Invalid shots (number_of_shots={})'.format(number_of_shots))

    def __validate_number_of_clbits(self, experiment: QasmQobjExperiment) -> None:
        """ Validate the number of classical bits

        Checks whether the number of classical bits has a value cQASM can support.

        1.  When number of classical bits is less than 1 an error is raised.
        2.  When binary controlled gates are used and the number of classical registers
            is greater than the number of qubits an error is raised.

            When using binary controlled gates in Qiskit, we can have something like:

            .. code::

                q = QuantumRegister(2)
                c = ClassicalRegister(4)
                circuit = QuantumCircuit(q, c)
                circuit.h(q[0]).c_if(c, 15)

            Because cQASM has the same number of classical registers as qubits (2 in this case),
            this circuit cannot be translated to valid cQASM.

        :param experiment: The experiment with gate operations and header.

        :raises QiskitBackendError: When the value is not correct.
        """
        number_of_clbits = experiment.header.memory_slots
        if number_of_clbits < 1:
            raise QiskitBackendError("Invalid amount of classical bits ({})!".format(number_of_clbits))

        measurements = self._collect_measurements(experiment)
        number_of_classical_bits = measurements['number_of_clbits']
        max_measurement_index = max(measurement[1] for measurement in measurements['measurements'])
        if max_measurement_index >= number_of_classical_bits:
            raise QiskitBackendError(f"Number of classical bits ({number_of_classical_bits}) is not sufficient for "
                                     f"storing the outcomes of the experiment")

        if Backend.configuration(self).conditional:
            number_of_qubits = experiment.header.n_qubits
            if number_of_clbits > number_of_qubits:
                # no problem when there are no conditional gate operations
                for instruction in experiment.instructions:
                    if hasattr(instruction, 'conditional'):
                        raise QiskitBackendError("Number of classical bits must be less than or equal to the"
                                                 " number of qubits when using conditional gate operations")

    @staticmethod
    def __validate_full_state_projection(experiment: QasmQobjExperiment) -> bool:
        """ Determine if full state projection can be used

        FSP (Full State Projection) can be used when no measurements are found in the circuit or when no
        other gates are found after measurements.

        :param experiment: The experiment with gate operations and header.

        :return:
            True when FSP can be used, otherwise False.
        """
        fsp = True
        measurement_found = False
        for instruction in experiment.instructions:
            if instruction.name == 'measure':
                measurement_found = True
            elif measurement_found:
                fsp = False
        return fsp

    @staticmethod
    def __validate_unsupported_measurements(experiment: QasmQobjExperiment) -> None:
        """ Validate unsupported measurements

        When using non-FSP (not full state projection) certain measurements cannot be handled correctly because
        cQASM isn't as flexible as Qiskit in measuring to specific classical bits.
        Therefore some Qiskit constructions are not supported in QI:

            1. When a quantum register is measured to different classical registers
            2. When a classical register is used for the measurement of more than one quantum register

        :param experiment: The experiment with gate operations and header.

        :raises QiskitBackendError: When the circuit contains an invalid non-FSP measurement
        """
        measurements: List[List[int]] = []
        for instruction in experiment.instructions:
            if instruction.name == 'measure':
                for q, m in measurements:
                    if q == instruction.qubits[0] and m != instruction.memory[0]:
                        raise QiskitBackendError('Measurement of qubit {} to different classical registers '
                                                 'is not supported'.format(q))
                    if q != instruction.qubits[0] and m == instruction.memory[0]:
                        raise QiskitBackendError('Measurement of different qubits to the same classical register {0} '
                                                 'is not supported'.format(m))
                measurements.append([instruction.qubits[0], instruction.memory[0]])

    @staticmethod
    def _collect_measurements(experiment: QasmQobjExperiment) -> Dict[str, Any]:
        """ Determines the measured qubits and classical bits.

        The full-state measured
        qubits is returned when no measurements are present in the compiled circuit.

        :param experiment: The experiment with gate operations and header.

        :return:
            The dict contains measurements, which is a list of lists, for each measurement the list contains
            a list of [qubit_index, classical_bit_index], which represents the measurement of a qubit to a
            classical bit, and the second field in the dict is the number of classical bits (int).
        """
        header = experiment.header
        number_of_qubits = header.n_qubits
        number_of_clbits = header.memory_slots

        operations = experiment.instructions
        measurements = [[number_of_qubits - 1 - m.qubits[0],
                         number_of_clbits - 1 - m.memory[0]]
                        for m in operations if m.name == 'measure']
        if not measurements:
            measurements = [[index, index] for index in range(number_of_qubits)]
        return {'measurements': measurements, 'number_of_clbits': number_of_clbits}

    @staticmethod
    def __qubit_to_classical_hex(qubit_register: str, measurements: Dict[str, Any], number_of_qubits: int) -> str:
        """ This function converts the qubit register data to the hexadecimal representation of the classical state.

        :param qubit_register: The measured value of the qubits represented as int.
        :param measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).
        :param number_of_qubits: Number of qubits used in the algorithm.

        :return:
            The hexadecimal value of the classical state.
        """
        qubit_state = ('{0:0{1}b}'.format(int(qubit_register), number_of_qubits))
        classical_state = ['0'] * measurements['number_of_clbits']
        for q, c in measurements['measurements']:
            classical_state[c] = qubit_state[q]
        classical_state_str = ''.join(classical_state)
        classical_state_hex = hex(int(classical_state_str, 2))
        return classical_state_hex

    @staticmethod
    def __convert_histogram(result: Dict[str, Any], measurements: Dict[str, Any]) -> Dict[str, float]:
        """Convert histogram

        The Quantum Inspire backend always uses full state projection. The SDK user
        can measure not all qubits and change the combined classical bits. This function
        converts the result to a histogram output that represents the probabilities
        measured with the classical bits.

        :param result: The result output from the Quantum Inspire backend with full-
                    state projection histogram output.
        :param measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).

        :return:
            The resulting full state histogram with probabilities.
        """
        output_histogram_probabilities: Dict[str, float] = defaultdict(lambda: 0)
        number_of_qubits = result['number_of_qubits']
        state_probability: Dict[str, float] = result['histogram']
        for qubit_register, probability in state_probability.items():
            classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(qubit_register, measurements,
                                                                                 number_of_qubits)
            output_histogram_probabilities[classical_state_hex] += probability

        sorted_histogram_probabilities: List[Tuple[str, float]] = sorted(output_histogram_probabilities.items(),
                                                                         key=lambda kv: int(kv[0], 16))
        return dict(sorted_histogram_probabilities)

    def __convert_result_data(self, result: Dict[str, Any], measurements: Dict[str, Any]) -> Tuple[Dict[str, int],
                                                                                                   List[str]]:
        """Conver result data

        The Quantum Inspire backend returns the single shot values as raw data. This function
        converts this list of single shot values to hexadecimal memory data according the Qiskit spec.
        From this memory data the counts histogram is constructed by counting the single shot values.

        .. note::
            When shots = 1, the backend returns an empty list as raw_data. This is a special case. In this case the
            resulting memory data consists of 1 value and the count histogram consists of 1 instance of this value.
            To determine this value a random float is generated in the range [0, 1). With this random number the
            value from this probabilities histogram is taken where the added probabilities is greater this random
            number.

            Example:

                Probability histogram is ``{[0x0, 0.2], [0x3, 0.4], [0x5, 0.1], [0x6, 0.3]}``.

                When random is in the range [0, 0.2) the first value of the probability histogram is taken (0x0).

                When random is in the range [0.2, 0.6) the second value of the probability histogram is taken (0x3).

                When random is in the range [0.6, 0.7) the third value of the probability histogram is taken (0x5).

                When random is in the range [0.7, 1) the last value of the probability histogram is taken (0x6).

        :param result: The result output from the Quantum Inspire backend with full-
                    state projection histogram output.
        :param measurements: The dictionary contains a measured qubits/classical bits map (list) and the
                          number of classical bits (int).

        :return:
            The result consists of two formats for the result. The first result is the histogram with count data,
            the second result is a list with converted hexadecimal memory values for each shot.
        """
        memory_data = []
        histogram_data: Dict[str, int] = defaultdict(lambda: 0)
        number_of_qubits: int = result['number_of_qubits']
        raw_data = self.__api.get_raw_data_from_result(result['id'])
        if raw_data:
            for raw_qubit_register in raw_data:
                classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(str(raw_qubit_register),
                                                                                     measurements, number_of_qubits)
                memory_data.append(classical_state_hex)
            histogram_data = {elem: count for elem, count in Counter(memory_data).items()}
        else:
            state_probabilities = result['histogram']
            random_probability = np.random.rand()
            sum_probability = 0.0
            for qubit_register, probability in state_probabilities.items():
                sum_probability += probability
                if random_probability < sum_probability:
                    classical_state_hex = QuantumInspireBackend.__qubit_to_classical_hex(qubit_register, measurements,
                                                                                         number_of_qubits)
                    memory_data.append(classical_state_hex)
                    histogram_data[classical_state_hex] = 1
                    break

        sorted_histogram_data: List[Tuple[str, int]] = sorted(histogram_data.items(), key=lambda kv: int(kv[0], 16))
        return dict(sorted_histogram_data), memory_data