[PYTHON] Qiskit-Quellcode lesen ~ Terra: Backend lesen Get, Call, Get Result

Was machst du?

Ich mache Blueqat, eine Quantencomputer-Bibliothek, und ich plane, den Quellcode von Qiskit, einer Quantencomputer-Bibliothek, zu lesen.

In Fortsetzung von Letztes Mal lesen wir diesmal den Prozess des Lesens der Backend-Erfassung, des Aufrufs und des Erhaltens von Ergebnissen. Anstatt die Quelle des Simulators selbst zu lesen, werden wir uns mit den Schnittstellen rund um das Backend befassen.

Qiskit Übersicht

Qiskit ist eine von IBM entwickelte Open-Source-Quantencomputerbibliothek.

Qiskit ist wie unten gezeigt in Pakete unterteilt, aber bei der Installation ist es weniger mühsam, es gemeinsam mit "pip install qiskit" zu installieren, als es separat zu tun.

Paket Rolle
Qiskit Terra Dies ist das Hauptpaket. Es enthält eine Klasse zum Erstellen einer Schaltung, eine Funktion zum Transpilieren der Schaltung für die tatsächliche Maschine, eine Funktion zum Aufrufen der API und zum Werfen auf die tatsächliche Maschine usw.
Qiskit Aer Enthält einen Quantenschaltungssimulator, der normalerweise von Qiskit Terra aufgerufen wird
Qiskit Ignis Dies ist eine Bibliothek für diejenigen, die das Rauschen bekämpfen möchten, wenn sie eine Quantenschaltung auf einer tatsächlichen Maschine betreiben. Ich habe noch nie benutzt
Qiskit Aqua Eine Bibliothek, die die Verwendung von Quantenalgorithmen vereinfacht

Diesmal lese ich einen Teil von Qiskit Terra.

https://github.com/Qiskit/qiskit-terra

Insbesondere der in README.md geschriebene Code

from qiskit import *
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0,1], [0,1])
backend_sim = BasicAer.get_backend('qasm_simulator')
result = execute(qc, backend_sim).result()
print(result.get_counts(qc))

Lesen Sie dazu den Ablauf nach "backend_sim = BasicAer.get_backend (" qasm_simulator ")". Ich werde jedoch die Details des Simulatorinhalts oder der Transpile nicht lesen.

Lesen Sie weiter den Hauptzweig auf GitHub. Die aktuelle Commit-ID lautet e7be587, wird jedoch häufig aktualisiert und kann sich bis zum Ende des Artikels ändern. Bitte beachten Sie.

Lesen Sie die Zeile "BasicAer.get_backend"

backend_sim = BasicAer.get_backend('qasm_simulator')

Lesen Sie diese Zeile. BasicAer befindet sich in terras qiskit / provider / basicaer / init.py

BasicAer = BasicAerProvider()

Ist definiert als.

Lesen Sie BasicAerProvider

Lesen Sie den "BasicAerProvider" in qiskit / provider / basicaer / basicaerprovider.py.

SIMULATORS = [
    QasmSimulatorPy,
    StatevectorSimulatorPy,
    UnitarySimulatorPy
]


class BasicAerProvider(BaseProvider):
    """Provider for Basic Aer backends."""

    def __init__(self, *args, **kwargs):
        super().__init__(args, kwargs)

        # Populate the list of Basic Aer backends.
        self._backends = self._verify_backends()

    #Abkürzung

    def _verify_backends(self):
        """
        Return the Basic Aer backends in `BACKENDS` that are
        effectively available (as some of them might depend on the presence
        of an optional dependency or on the existence of a binary).
        Returns:
            dict[str:BaseBackend]: a dict of Basic Aer backend instances for
                the backends that could be instantiated, keyed by backend name.
        """
        ret = OrderedDict()
        for backend_cls in SIMULATORS:
            try:
                backend_instance = self._get_backend_instance(backend_cls)
                backend_name = backend_instance.name()
                ret[backend_name] = backend_instance
            except QiskitError as err:
                # Ignore backends that could not be initialized.
                logger.info('Basic Aer backend %s is not available: %s',
                            backend_cls, str(err))
        return ret

Lesen Sie vorerst das __init__ der übergeordneten Klasse BaseProvider.

class BaseProvider(ABC):
    """Base class for a Backend Provider."""

    def __init__(self, *args, **kwargs):
        pass

Ich mache gar nichts.

Lesen Sie "BasicAerProvider._verify_backends"

fortsetzen

self._backends = self._verify_backends()

Lesen Sie _verify_backends in. Ich habe den obigen Code eingefügt, aber es ist wirklich schlecht, etwas anderes zu tun, als mit dem Namen "verify" zu verifizieren ... Stell es zur Seite. Wir können die Struktur sehen, dass "BasicAer" ein Anbieter ist und ein Anbieter ein "Backend" hat.

Es scheint, dass die in [SIMULATORS "definierten Klassen" [QasmSimulatorPy, StatevectorSimulatorPy, UnitarySimulatorPy] "jeweils" _get_backend_instance "sind und in ein (geordnetes) Wörterbuch gepackt und zurückgegeben werden.

Lesen wir "_get_backend_instance". Die Dokumentzeichenfolge wird gelöscht und in Anführungszeichen gesetzt.

    def _get_backend_instance(self, backend_cls):
        # Verify that the backend can be instantiated.
        try:
            backend_instance = backend_cls(provider=self)
        except Exception as err:
            raise QiskitError('Backend %s could not be instantiated: %s' %
                              (backend_cls, err))

        return backend_instance

Ich erstelle nur eine Instanz der Backend-Klasse, übergebe aber den Anbieter selbst an die Instanz. Der Anbieter und das Backend stehen in einer gegenseitigen Referenzbeziehung.

Lesen Sie QasmSimulatorPy .__ init__

Diese Backend-Klassen selbst qiskit / provider / basicaer / qasm_simulator.py, qiskit / provider / basicaer / statevector_simulator. py, [qiskit / provider / basicaer / unitary_simulator.py](https: // github. Es ist in com / Qiskit / qiskit-terra / blob / master / qiskit / provider / basicaer / unitary_simulator.py definiert. Schauen wir uns einfach QasmSimulatorPy .__ init__ an.

class QasmSimulatorPy(BaseBackend):
    """Python implementation of a qasm simulator."""

    MAX_QUBITS_MEMORY = int(log2(local_hardware_info()['memory'] * (1024 ** 3) / 16))

    DEFAULT_CONFIGURATION = {
        'backend_name': 'qasm_simulator',
        'backend_version': '2.0.0',
        'n_qubits': min(24, MAX_QUBITS_MEMORY),
        'url': 'https://github.com/Qiskit/qiskit-terra',
        'simulator': True,
        'local': True,
        'conditional': True,
        'open_pulse': False,
        'memory': True,
        'max_shots': 65536,
        'coupling_map': None,
        'description': 'A python simulator for qasm experiments',
        'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'unitary'],
        'gates': [
            {
                'name': 'u1',
                'parameters': ['lambda'],
                'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }'
            },
            {
                'name': 'u2',
                'parameters': ['phi', 'lambda'],
                'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }'
            },
            {
                'name': 'u3',
                'parameters': ['theta', 'phi', 'lambda'],
                'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }'
            },
            {
                'name': 'cx',
                'parameters': ['c', 't'],
                'qasm_def': 'gate cx c,t { CX c,t; }'
            },
            {
                'name': 'id',
                'parameters': ['a'],
                'qasm_def': 'gate id a { U(0,0,0) a; }'
            },
            {
                'name': 'unitary',
                'parameters': ['matrix'],
                'qasm_def': 'unitary(matrix) q1, q2,...'
            }
        ]
    }

    DEFAULT_OPTIONS = {
        "initial_statevector": None,
        "chop_threshold": 1e-15
    }

    # Class level variable to return the final state at the end of simulation
    # This should be set to True for the statevector simulator
    SHOW_FINAL_STATE = False

    def __init__(self, configuration=None, provider=None):
        super().__init__(configuration=(
            configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)),
                         provider=provider)

        # Define attributes in __init__.
        self._local_random = np.random.RandomState()
        self._classical_memory = 0
        self._classical_register = 0
        self._statevector = 0
        self._number_of_cmembits = 0
        self._number_of_qubits = 0
        self._shots = 0
        self._memory = False
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
        self._qobj_config = None
        # TEMP
        self._sample_measure = False

Ich habe das Gefühl, verschiedene Einstellungen vorgenommen zu haben. Was Sie tun, ist nur das Initialisieren von Variablen, und es sieht nicht so aus, als würden Sie viel tun. Sie können nur bis zu 24 Quantenbits simulieren. Ich wusste es nicht. (Ich habe den Eindruck, dass es ziemlich stark eingestellt ist. Sie sollten in der Lage sein, auch mit einem normalen Computer Ihr Bestes zu geben.)

Es scheint, dass das Backend selbst einen Zustand aufweist, der in Bearbeitung zu sein scheint, wie z. B. die Anzahl der Aufnahmen und das Gedächtnis. (Blueqat wagt es, eine solche Implementierung zu vermeiden) Die Idee hier wird möglicherweise etwas klarer, wenn Sie den Code lesen.

Was war "Basic Aer"?

--BasicAer ist eine Instanz von BasicAerProvider

Ich verstehe. Schauen wir uns als nächstes BasicAer.get_backend an.

Lesen Sie BasicAer.get_backend


    def get_backend(self, name=None, **kwargs):
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                name = resolved_name
            except LookupError:
                raise QiskitBackendNotFoundError(
                    "The '{}' backend is not installed in your system.".format(name))

        return super().get_backend(name=name, **kwargs)

Obwohl ich "self._backends" im Wörterbuch habe, bin ich neugierig, dass ich es mache, ohne das Wörterbuch nachzuschlagen. Lesen wir resolve_backend_name.

Lesen Sie resolve_backend_name

resolve_backend_name ist in [qiskit / provider / providerutils.py] definiert (https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/providers/providerutils.py).

def resolve_backend_name(name, backends, deprecated, aliased):
    """Resolve backend name from a deprecated name or an alias.
    A group will be resolved in order of member priorities, depending on
    availability.
    Args:
        name (str): name of backend to resolve
        backends (list[BaseBackend]): list of available backends.
        deprecated (dict[str: str]): dict of deprecated names.
        aliased (dict[str: list[str]]): dict of aliased names.
    Returns:
        str: resolved name (name of an available backend)
    Raises:
        LookupError: if name cannot be resolved through regular available
            names, nor deprecated, nor alias names.
    """
    available = [backend.name() for backend in backends]

    resolved_name = deprecated.get(name, aliased.get(name, name))
    if isinstance(resolved_name, list):
        resolved_name = next((b for b in resolved_name if b in available), "")

    if resolved_name not in available:
        raise LookupError("backend '{}' not found.".format(name))

    if name in deprecated:
        logger.warning("Backend '%s' is deprecated. Use '%s'.", name,
                       resolved_name)

    return resolved_name

Erstellen Sie mit available eine Liste mit Backend-Namen.

"veraltet" können diejenigen, die Qiskit seit einiger Zeit verwenden, unter "BasicAerProvider._deprecated_backend_names ()" sehen.

    @staticmethod
    def _deprecated_backend_names():
        """Returns deprecated backend names."""
        return {
            'qasm_simulator_py': 'qasm_simulator',
            'statevector_simulator_py': 'statevector_simulator',
            'unitary_simulator_py': 'unitary_simulator',
            'local_qasm_simulator_py': 'qasm_simulator',
            'local_statevector_simulator_py': 'statevector_simulator',
            'local_unitary_simulator_py': 'unitary_simulator',
            'local_unitary_simulator': 'unitary_simulator',
            }

Es ist ein Wörterbuch von "{" alter Backend-Name ":" aktiver Backend-Name "}". Wenn der gesuchte Backend-Name "veraltet" ist, konvertieren Sie ihn in den aktiven Backend-Namen.

Für "Alias" suchte ich nach "Qiskit-Terra" und "Qiskit-Aer", konnte aber nichts finden, was nicht leer war, aber der gesuchte Backend-Name befand sich im "Alias" -Wörterbuch. Wenn ja, scheint es die Liste der Aliase abzurufen. Da die Liste mehrere Aliase enthält, machen Sie sie zum allerersten "verfügbaren" Backend-Namen. Wenn keiner von ihnen "verfügbar" ist, machen Sie ihn zu einer leeren Zeichenfolge.

Wenn die Namenskonvertierung abgeschlossen ist, überprüfen Sie das Backend, das dem konvertierten Namen entspricht, geben Sie den Namen zurück, falls vorhanden, und lösen Sie eine Ausnahme aus, wenn nicht.

Lesen Sie die übergeordnete Klasse get_backend ()

Nachdem Sie den Namen konvertiert und einen "verfügbaren" Namen erhalten haben,

        return super().get_backend(name=name, **kwargs)

Ich habe gesagt. Lesen wir also "BaseProvider.get_backend ()". Lassen Sie die Dokumentzeichenfolge weg.

    def get_backend(self, name=None, **kwargs):
        backends = self.backends(name, **kwargs)
        if len(backends) > 1:
            raise QiskitBackendNotFoundError('More than one backend matches the criteria')
        if not backends:
            raise QiskitBackendNotFoundError('No backend matches the criteria')

        return backends[0]

Gibt das erste Element der Liste oder etwas zurück, das von "BasicAerProvider.backends (name)" zurückgegeben wurde.

Lesen Sie BasicAerProvider.backends.

    def backends(self, name=None, filters=None, **kwargs):
        # pylint: disable=arguments-differ
        backends = self._backends.values()

        # Special handling of the `name` parameter, to support alias resolution
        # and deprecated names.
        if name:
            try:
                resolved_name = resolve_backend_name(
                    name, backends,
                    self._deprecated_backend_names(),
                    {}
                )
                backends = [backend for backend in backends if
                            backend.name() == resolved_name]
            except LookupError:
                return []

        return filter_backends(backends, filters=filters, **kwargs)

Es ruft auch resolve_backend_name auf. Sofern das "veraltete" nicht im Umlauf ist oder so etwas Seltsames, sollten Sie das gleiche Ergebnis erzielen, egal wie oft Sie es versuchen. Dieses Mal ziehe ich das Backend selbst heraus, nicht den Namen.

Was filter_backends betrifft, ist es eine lange Zeit, obwohl ich nicht viel getan habe. Wenn Sie also interessiert sind, qiskit / provider / providerutils.py Weitere Informationen finden Sie unter /qiskit/providers/providerutils.py).

Es werden nur Dokumentzeichenfolgen und Kommentare zitiert.

def filter_backends(backends, filters=None, **kwargs):
    """Abkürzung
    Args:
        backends (list[BaseBackend]): list of backends.
        filters (callable): filtering conditions as a callable.
        **kwargs: dict of criteria.
    Returns:
        list[BaseBackend]: a list of backend instances matching the
            conditions.
    """
    # Inspect the backends to decide which filters belong to
    # backend.configuration and which ones to backend.status, as it does
    # not involve querying the API.

    # 1. Apply backend.configuration filtering.
    # 2. Apply backend.status filtering (it involves one API call for
    # each backend).
    # 3. Apply acceptor filter.

kwargs gibt Bedingungen für backend.configuration und backend.status an. Wenn diese auf gemischte Weise angegeben werden, werden sie auf der Seite "filter_backends" sortiert. Wenn Sie die Funktion dem Argument "filter" zuweisen, wird das Ergebnis der Filterung mit der in Python integrierten Funktion "filter" zurückgegeben.

Die Geschichte geht übrigens schief. Sie können None als Argument an die in Python integrierte Filterfunktion übergeben. Ich wusste es nicht.

Return an iterator yielding those items of iterable for which function(item) is true. If function is None, return the items that are true.

Es scheint.

list(filter(None, [1, 2, 0, 3, "", "a"]))                                                        
# => [1, 2, 3, 'a']

Wie auch immer, ich konnte ein Backend bekommen, das dem Namen entsprach, obwohl ich durcheinander war. mit diesem

backend_sim = BasicAer.get_backend('qasm_simulator')

Sie können die Zeile lesen.

Lesen Sie "Ausführen"

fortsetzen

result = execute(qc, backend_sim).result()

Lesen Sie in der Zeile execute. Unter qiskit / execute.py wird docstring gelöscht und in Anführungszeichen gesetzt.

def execute(experiments, backend,
            basis_gates=None, coupling_map=None,  # circuit transpile options
            backend_properties=None, initial_layout=None,
            seed_transpiler=None, optimization_level=None, pass_manager=None,
            qobj_id=None, qobj_header=None, shots=1024,  # common run options
            memory=False, max_credits=10, seed_simulator=None,
            default_qubit_los=None, default_meas_los=None,  # schedule run options
            schedule_los=None, meas_level=2, meas_return='avg',
            memory_slots=None, memory_slot_size=100, rep_time=None, parameter_binds=None,
            **run_config):
    # transpiling the circuits using given transpile options
    experiments = transpile(experiments,
                            basis_gates=basis_gates,
                            coupling_map=coupling_map,
                            backend_properties=backend_properties,
                            initial_layout=initial_layout,
                            seed_transpiler=seed_transpiler,
                            optimization_level=optimization_level,
                            backend=backend,
                            pass_manager=pass_manager,
                            )

    # assembling the circuits into a qobj to be run on the backend
    qobj = assemble(experiments,
                    qobj_id=qobj_id,
                    qobj_header=qobj_header,
                    shots=shots,
                    memory=memory,
                    max_credits=max_credits,
                    seed_simulator=seed_simulator,
                    default_qubit_los=default_qubit_los,
                    default_meas_los=default_meas_los,
                    schedule_los=schedule_los,
                    meas_level=meas_level,
                    meas_return=meas_return,
                    memory_slots=memory_slots,
                    memory_slot_size=memory_slot_size,
                    rep_time=rep_time,
                    parameter_binds=parameter_binds,
                    backend=backend,
                    **run_config
                    )

    # executing the circuits on the backend and returning the job
    return backend.run(qobj, **run_config)

Schauen Sie sich "transpile" an

Dieses Mal werde ich nicht auf die Details von Transpile eingehen. Ich schaue wirklich nur auf die Oberfläche.

Sie finden es unter qiskit / compiler / transpile.py. Ich werde es weglassen, weil der Docstring sehr lang ist, aber es ist interessant und ich empfehle, es zu lesen.

def transpile(circuits,
              backend=None,
              basis_gates=None, coupling_map=None, backend_properties=None,
              initial_layout=None, seed_transpiler=None,
              optimization_level=None,
              pass_manager=None, callback=None, output_name=None):
    # transpiling schedules is not supported yet.
    if isinstance(circuits, Schedule) or \
            (isinstance(circuits, list) and all(isinstance(c, Schedule) for c in circuits)):
        return circuits

    if optimization_level is None:
        config = user_config.get_config()
        optimization_level = config.get('transpile_optimization_level', None)

    # Get TranspileConfig(s) to configure the circuit transpilation job(s)
    circuits = circuits if isinstance(circuits, list) else [circuits]
    transpile_configs = _parse_transpile_args(circuits, backend, basis_gates, coupling_map,
                                              backend_properties, initial_layout,
                                              seed_transpiler, optimization_level,
                                              pass_manager, callback, output_name)
    # Check circuit width against number of qubits in coupling_map(s)
    coupling_maps_list = list(config.coupling_map for config in transpile_configs)
    for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list):
        # If coupling_map is not None
        if isinstance(parsed_coupling_map, CouplingMap):
            n_qubits = len(circuit.qubits)
            max_qubits = parsed_coupling_map.size()
            if n_qubits > max_qubits:
                raise TranspilerError('Number of qubits ({}) '.format(n_qubits) +
                                      'in {} '.format(circuit.name) +
                                      'is greater than maximum ({}) '.format(max_qubits) +
                                      'in the coupling_map')
    # Transpile circuits in parallel
    circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_configs)))

    if len(circuits) == 1:
        return circuits[0]
    return circuits

In tatsächlichen Maschinen gibt es Gates, die nicht mit CNOT verbunden sind. Es scheint also, dass sie daran arbeiten, Schaltkreise zu erstellen, während sie diese zuweisen. Da die Berechnung selbst schwierig ist, wird sie außerdem parallel berechnet. Selbst wenn Sie das Argument weglassen, werden die Einstellungen entsprechend vom Back-End abgerufen, da dem Back-End viele Einstellungen hinzugefügt wurden.

Konvertiert die Schaltung und gibt die Schaltung selbst zurück.

Schauen Sie sich "montieren" an

Sehen Sie sich das assemble in qiskit / compiler / assemble.py auf die gleiche Weise an. (Docstring weggelassen)

def assemble(experiments,
             backend=None,
             qobj_id=None, qobj_header=None,
             shots=1024, memory=False, max_credits=None, seed_simulator=None,
             qubit_lo_freq=None, meas_lo_freq=None,
             qubit_lo_range=None, meas_lo_range=None,
             schedule_los=None, meas_level=2, meas_return='avg', meas_map=None,
             memory_slot_size=100, rep_time=None, parameter_binds=None,
             **run_config):
    experiments = experiments if isinstance(experiments, list) else [experiments]
    qobj_id, qobj_header, run_config_common_dict = _parse_common_args(backend, qobj_id, qobj_header,
                                                                      shots, memory, max_credits,
                                                                      seed_simulator, **run_config)

    # assemble either circuits or schedules
    if all(isinstance(exp, QuantumCircuit) for exp in experiments):
        run_config = _parse_circuit_args(parameter_binds, **run_config_common_dict)

        # If circuits are parameterized, bind parameters and remove from run_config
        bound_experiments, run_config = _expand_parameters(circuits=experiments,
                                                           run_config=run_config)
        return assemble_circuits(circuits=bound_experiments, qobj_id=qobj_id,
                                 qobj_header=qobj_header, run_config=run_config)

    elif all(isinstance(exp, ScheduleComponent) for exp in experiments):
        run_config = _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq,
                                       qubit_lo_range, meas_lo_range,
                                       schedule_los, meas_level, meas_return,
                                       meas_map, memory_slot_size, rep_time,
                                       **run_config_common_dict)

        return assemble_schedules(schedules=experiments, qobj_id=qobj_id,
                                  qobj_header=qobj_header, run_config=run_config)

    else:
        raise QiskitError("bad input to assemble() function; "
                          "must be either circuits or schedules")

Es scheint, dass Sie die Schaltung und den Impulsplan zusammenstellen können. Der Typ des zurückzugebenden Werts ist "QObj", und im Fall einer Schaltung wird der Typ "QasmQObj" zurückgegeben.

Wenn Sie nicht wissen, welche Art von Datenstruktur der Typ "QasmQobj" ist, werden Sie danach in Schwierigkeiten geraten

from qiskit import * 
qc = QuantumCircuit(2, 2) 
qc.h(0) 
qc.cx(0, 1) 
qc.measure([0,1], [0,1]) 
backend_sim = BasicAer.get_backend('qasm_simulator') 
tran = transpile(qc, backend=backend_sim)
asm = assemble(qc, backend=backend_sim)

Mit Blick auf das "Asm" von war es wie folgt.

QasmQobj(
    config=QasmQobjConfig(
        memory=False,
        memory_slots=2,
        n_qubits=2,
        parameter_binds=[],
        shots=1024),
    experiments=[
        QasmQobjExperiment(
            config=QasmQobjExperimentConfig(memory_slots=2, n_qubits=2),
            header=QobjExperimentHeader(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            instructions=[
                QasmQobjInstruction(
                    name='u2', params=[0, 3.14159265358979], qubits=[0]),
                QasmQobjInstruction(name='cx', qubits=[0, 1]),
                QasmQobjInstruction(memory=[0], name='measure', qubits=[0]),
                QasmQobjInstruction(memory=[1], name='measure', qubits=[1])
            ])
    ],
    header=QobjHeader(backend_name='qasm_simulator', backend_version='2.0.0'),
    qobj_id='06682d2e-bfd8-4dba-ba8e-4e46492c1609',
    schema_version='1.1.0',
    type='QASM')

Ich denke, Sie können es ohne Probleme lesen. Das H-Gate wird übrigens in "u2 (0, π)" umgewandelt. Dies ist ein äquivalenter Ausdruck.

Siehe backend.run

Nachdem wir die Schaltung als nächstes auf "QasmQobj" umgestellt haben

    return backend.run(qobj, **run_config)

Ich werde lesen. qiskit / provider / basicaer / qasm_simulator.py

    def run(self, qobj, backend_options=None):
        """Run qobj asynchronously.
        Args:
            qobj (Qobj): payload of the experiment
            backend_options (dict): backend options
        Returns:
            BasicAerJob: derived from BaseJob
        Additional Information:
            backend_options: Is a dict of options for the backend. It may contain
                * "initial_statevector": vector_like
            The "initial_statevector" option specifies a custom initial
            initial statevector for the simulator to be used instead of the all
            zero state. This size of this vector must be correct for the number
            of qubits in all experiments in the qobj.
            Example::
                backend_options = {
                    "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2),
                }
        """
        self._set_options(qobj_config=qobj.config,
                          backend_options=backend_options)
        job_id = str(uuid.uuid4())
        job = BasicAerJob(self, job_id, self._run_job, qobj)
        job.submit()
        return job

Es ist schön, einen Anfangsvektor in die Optionen einfügen zu können. Es sieht so aus, als ob es vollständig vom Backend abhängig ist. Die Option "chop_threshold" scheint den Wert als "0" zu behandeln, wenn er bei der Simulation des Zustandsvektors kleiner oder gleich diesem Wert ist.

Lesen Sie QasmSimulatorPy._set_options

Lesen Sie weiter _set_options.

    def _set_options(self, qobj_config=None, backend_options=None):
        """Set the backend options for all experiments in a qobj"""
        # Reset default options
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
        if backend_options is None:
            backend_options = {}

        # Check for custom initial statevector in backend_options first,
        # then config second
        if 'initial_statevector' in backend_options:
            self._initial_statevector = np.array(backend_options['initial_statevector'],
                                                 dtype=complex)
        elif hasattr(qobj_config, 'initial_statevector'):
            self._initial_statevector = np.array(qobj_config.initial_statevector,
                                                 dtype=complex)
        if self._initial_statevector is not None:
            # Check the initial statevector is normalized
            norm = np.linalg.norm(self._initial_statevector)
            if round(norm, 12) != 1:
                raise BasicAerError('initial statevector is not normalized: ' +
                                    'norm {} != 1'.format(norm))
        # Check for custom chop threshold
        # Replace with custom options
        if 'chop_threshold' in backend_options:
            self._chop_threshold = backend_options['chop_threshold']
        elif hasattr(qobj_config, 'chop_threshold'):
            self._chop_threshold = qobj_config.chop_threshold

Aufgrund der Tatsache, dass das Backend selbst Optionen hat

        # Reset default options
        self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
        self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]

Ist an einem solchen Ort herausgekommen. Wie auch immer, ich interpretiere die beiden Optionen einfach und setze sie in qobj.

Lesen Sie BasicAerJob .__ init__

        job_id = str(uuid.uuid4())
        job = BasicAerJob(self, job_id, self._run_job, qobj)

Es hat eine geeignete UUID als job_id. Ich mache BasicAerJob als Job. self._run_job ist eine Methode, aber wir übergeben die Methode so wie sie ist. Mal sehen, wenn wir später angerufen werden.

qiskit/providers/basicaer/basicaerjob.py

class BasicAerJob(BaseJob):
    """BasicAerJob class.
    Attributes:
        _executor (futures.Executor): executor to handle asynchronous jobs
    """

    if sys.platform in ['darwin', 'win32']:
        _executor = futures.ThreadPoolExecutor()
    else:
        _executor = futures.ProcessPoolExecutor()

    def __init__(self, backend, job_id, fn, qobj):
        super().__init__(backend, job_id)
        self._fn = fn
        self._qobj = qobj
        self._future = None

Ich habe gerade die Daten erstellt und noch nichts getan. Ich war ein wenig besorgt über die Verwendung des Thread-Pools unter Windows und Mac und des Prozess-Pools unter anderen. Beim Multithreading kann auch bei mehreren CPUs nur ein Thread gleichzeitig ausgeführt werden. Seien wir vorsichtig, wie es verwendet wird.

Lesen Sie BasicAerJob.submit

        job.submit()
        return job

Ich reiche einen Job ein und gebe dann einen Job zurück. Lesen wir "BasicAerJob.submit".

    def submit(self):
        """Submit the job to the backend for execution.
        Raises:
            QobjValidationError: if the JSON serialization of the Qobj passed
            during construction does not validate against the Qobj schema.
            JobError: if trying to re-submit the job.
        """
        if self._future is not None:
            raise JobError("We have already submitted the job!")

        validate_qobj_against_schema(self._qobj)
        self._future = self._executor.submit(self._fn, self._job_id, self._qobj)
        validate_qobj_against_schema(self._qobj)

Entspricht Qobj dem Format des in einer anderen Datei definierten JSON-Schemas? Wurde nur mit der externen Bibliothek jsonschema verifiziert. Es ist nicht sehr interessant, also werde ich es weglassen.

_executor war der ThreadPoolExecutor oder ProcessPoolExecutor in der Python-Standardbibliothekconcurrent.futures. Wir nennen diese "Submit" -Methode.

Lesen Sie die offiziellen Dokumente.

submit(fn, *args, **kwargs) Planen Sie die Ausführung des aufrufbaren Objekts "fn" als "fn (* args ** kwargs)" und stellen Sie die Ausführung des aufrufbaren Objekts [Future] dar (https://docs.python.org/en). /3/library/concurrent.futures.html#concurrent.futures.Future) Gibt ein Objekt zurück.

Mit anderen Worten, Sie erstellen ein "Future" -Objekt, um "self._fn (self._job_id, self._qobj)" für die Ausführung zu planen.

Dieses "Future" -Objekt dient zur asynchronen Ausführung. Während es von "_executor" hinter die Kulissen verschoben wird, wird der Vorgang auch dann fortgesetzt, wenn die Berechnung nicht abgeschlossen ist. (Wenn Sie einen Hamburger in einem Hamburgerladen bestellen, können Sie vor dem Schalter warten, bis er fertig ist. Wenn Sie jedoch ein Nummernschild erhalten, können Sie sich zuerst setzen oder etwas anderes tun und Ihre Zeit sinnvoll verbringen. Bei regulären Funktionsaufrufen müssen Sie warten, bis das Ergebnis zurückkommt. Bei asynchroner Ausführung können Sie jedoch etwas anderes tun, ohne darauf zu warten, dass es zurückkommt. Das Objekt "Future" erhält in Zukunft den Hamburger. Es ist wie ein Nummernschild

Lesen Sie QasmSimulatorPy._run_job

Erinnern an das, was "self._fn (self._job_id, self._qobj)" war, angetrieben von "_executor", "self made with" job = BasicAerJob (self, job_id, self._run_job, qobj) " Es war ._run_job (job_id, qobj) `.

Lesen wir also "QasmSimulatorPy._run_job".

    def _run_job(self, job_id, qobj):
        """Run experiments in qobj
        Args:
            job_id (str): unique id for the job.
            qobj (Qobj): job description
        Returns:
            Result: Result object
        """
        self._validate(qobj)
        result_list = []
        self._shots = qobj.config.shots
        self._memory = getattr(qobj.config, 'memory', False)
        self._qobj_config = qobj.config
        start = time.time()
        for experiment in qobj.experiments:
            result_list.append(self.run_experiment(experiment))
        end = time.time()
        result = {'backend_name': self.name(),
                  'backend_version': self._configuration.backend_version,
                  'qobj_id': qobj.qobj_id,
                  'job_id': job_id,
                  'results': result_list,
                  'status': 'COMPLETED',
                  'success': True,
                  'time_taken': (end - start),
                  'header': qobj.header.to_dict()}

        return Result.from_dict(result)

--self._validate (später lesen) --Stellen Sie die erforderlichen Informationen im Backend selbst ein

Lesen Sie QasmSimulatorPy._validate

    def _validate(self, qobj):
        """Semantic validations of the qobj which cannot be done via schemas."""
        n_qubits = qobj.config.n_qubits
        max_qubits = self.configuration().n_qubits
        if n_qubits > max_qubits:
            raise BasicAerError('Number of qubits {} '.format(n_qubits) +
                                'is greater than maximum ({}) '.format(max_qubits) +
                                'for "{}".'.format(self.name()))
        for experiment in qobj.experiments:
            name = experiment.header.name
            if experiment.config.memory_slots == 0:
                logger.warning('No classical registers in circuit "%s", '
                               'counts will be empty.', name)
            elif 'measure' not in [op.name for op in experiment.instructions]:
                logger.warning('No measurements in circuit "%s", '
                               'classical register will remain all zeros.', name)

Wir prüfen, ob die Anzahl der Qubits den Maximalwert nicht überschreitet und ob es ein klassisches Register gibt und ob eine Messung durchgeführt wird. (Kein klassisches Register, keine Messung wird als Warnung behandelt, kein Fehler)

View QasmSimulatorPy.run_experiment

Erinnern wir uns vorher daran, woraus die "Experimente" gemacht wurden.

    experiments=[
        QasmQobjExperiment(
            config=QasmQobjExperimentConfig(memory_slots=2, n_qubits=2),
            header=QobjExperimentHeader(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            instructions=[
                QasmQobjInstruction(
                    name='u2', params=[0, 3.14159265358979], qubits=[0]),
                QasmQobjInstruction(name='cx', qubits=[0, 1]),
                QasmQobjInstruction(memory=[0], name='measure', qubits=[0]),
                QasmQobjInstruction(memory=[1], name='measure', qubits=[1])
            ])
    ],

"QasmQobjExperiment" entspricht einer Quantenschaltung. Mit Qiskit können Sie eine Reihe von Quantenschaltungen packen und ausführen, aber diesmal scheint es nur eine zu geben. Da wir die Schleife von for experiment in qobj.experiments: ausgeführt haben, rufen wir für jede Schaltung run_experiment auf. Die Schaltung hat "Anweisungen", die Gates und Messungen entsprechen.

run_experiment ist wirklich lang, also werde ich es weglassen. Grob gesagt wird das Tor berechnet und die Messung wird für die Anzahl der "Schüsse" berechnet.

        return {'name': experiment.header.name,
                'seed_simulator': seed_simulator,
                'shots': self._shots,
                'data': data,
                'status': 'DONE',
                'success': True,
                'time_taken': (end - start),
                'header': experiment.header.to_dict()}

Gibt ein Ergebnis wie zurück. Außerdem ist "Daten"

        # Add data
        data = {'counts': dict(Counter(memory))}
        # Optionally add memory list
        if self._memory:
            data['memory'] = memory
        # Optionally add final statevector
        if self.SHOW_FINAL_STATE:
            data['statevector'] = self._get_statevector()
            # Remove empty counts and memory for statevector simulator
            if not data['counts']:
                data.pop('counts')
            if 'memory' in data and not data['memory']

Wie es hat Messdaten.

Lesen Sie qiskit.result.Result

Der Wörterbuchtyp wird durch "return Result.from_dict (result)" in "Result" geändert. qiskit/result/result.py

@bind_schema(ResultSchema)
class Result(BaseModel):
    """Model for Results.
    Please note that this class only describes the required fields. For the
    full description of the model, please check ``ResultSchema``.
    Attributes:
        backend_name (str): backend name.
        backend_version (str): backend version, in the form X.Y.Z.
        qobj_id (str): user-generated Qobj id.
        job_id (str): unique execution id from the backend.
        success (bool): True if complete input qobj executed correctly. (Implies
            each experiment success)
        results (list[ExperimentResult]): corresponding results for array of
            experiments of the input qobj
    """

    def __init__(self, backend_name, backend_version, qobj_id, job_id, success,
                 results, **kwargs):
        self.backend_name = backend_name
        self.backend_version = backend_version
        self.qobj_id = qobj_id
        self.job_id = job_id
        self.success = success
        self.results = results

Ursprünglich möchte ich "from_dict" lesen, aber eines der Merkmale von qiskit-terra ist, dass ich mein Bestes tue, um das JSON-Schema zu verarbeiten. from_dict ist in der übergeordneten Klasse BaseModel definiert und deren Inhalt sind

    @classmethod
    def from_dict(cls, dict_):
        """Deserialize a dict of simple types into an instance of this class.
        Note that this method requires that the model is bound with
        ``@bind_schema``.
        """
        try:
            data = cls.schema.load(dict_)
        except ValidationError as ex:
            raise ModelValidationError(
                ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None

        return data

Es ist sehr einfach. Erstellen Sie eine "ResultSchema" -Klasse getrennt von der "Result" -Klasse

class ResultSchema(BaseSchema):
    """Schema for Result."""

    # Required fields.
    backend_name = String(required=True)
    backend_version = String(required=True,
                             validate=Regexp('[0-9]+.[0-9]+.[0-9]+$'))
    qobj_id = String(required=True)
    job_id = String(required=True)
    success = Boolean(required=True)
    results = Nested(ExperimentResultSchema, required=True, many=True)

    # Optional fields.
    date = DateTime()
    status = String()
    header = Nested(ObjSchema)

Und so weiter geht es mir ziemlich gut. (Definiert unter /qiskit/result/models.py) Ich kann es nicht richtig lesen, aber ich denke, dass "ExperimentResultSchema" usw. rekursiv in Objekte umgewandelt werden, während bestätigt wird, dass sie diese Datentypen haben.

In jedem Fall handelt es sich um eine Datenauffüllung. Lassen Sie mich diesmal darauf verzichten.

Das "Ergebnis", das ich bewegte und zurückgab, war wie folgt.

Result(
    backend_name='qasm_simulator',
    backend_version='2.0.0',
    header=Obj(backend_name='qasm_simulator', backend_version='2.0.0'),
    job_id='65b162ae-fe6b-480a-85ba-8c890d8bbf3b',
    qobj_id='c75e9b2c-da7c-4dd6-96da-d132126e6dc0',
    results=[
        ExperimentResult(
            data=ExperimentResultData(counts=Obj(0x0=493, 0x3=531)),
            header=Obj(
                clbit_labels=[['c', 0], ['c', 1]],
                creg_sizes=[['c', 2]],
                memory_slots=2,
                n_qubits=2,
                name='circuit0',
                qreg_sizes=[['q', 2]],
                qubit_labels=[['q', 0], ['q', 1]]),
            meas_level=2,
            name='circuit0',
            seed_simulator=1480888590,
            shots=1024,
            status='DONE',
            success=True,
            time_taken=0.12355875968933105)
    ],
    status='COMPLETED',
    success=True,
    time_taken=0.12369537353515625)

Lesen Sie "BasicAerJob.result ()"

Ich habe die Mitte übersprungen, aber

result = execute(qc, backend_sim).result()

Die Ausführung (qc, backend_sim) von ist beendet, und wir werden uns die .result () ansehen. execute hat BasicAerJob zurückgegeben.

    @requires_submit
    def result(self, timeout=None):
        # pylint: disable=arguments-differ
        """Get job result. The behavior is the same as the underlying
        concurrent Future objects,
        https://docs.python.org/3/library/concurrent.futures.html#future-objects
        Args:
            timeout (float): number of seconds to wait for results.
        Returns:
            qiskit.Result: Result object
        Raises:
            concurrent.futures.TimeoutError: if timeout occurred.
            concurrent.futures.CancelledError: if job cancelled before completed.
        """
        return self._future.result(timeout=timeout)

Das erste "@ require_submit" sieht aus, wenn Sie ein "submit" durchgeführt haben. Unabhängig davon, ob es "übermittelt" wurde oder nicht, wurde beim Erstellen des "BasicAerJob" auf "self._future = None" gesetzt. Bei "Submission" ist das "Future" -Objekt in "self._future" enthalten, sodass es auf "None" gesetzt ist. Sie können dies herausfinden, indem Sie überprüfen, ob dies nicht der Fall ist.

Dies ruft normalerweise die "result" -Methode des "Future" -Objekts auf. Siehe die offizielle Dokumentation (https://docs.python.org/ja/3/library/concurrent.futures.html#concurrent.futures.Future).

result(timeout=None) Gibt den vom Aufruf zurückgegebenen Wert zurück. Wenn der Anruf noch nicht abgeschlossen ist, wartet diese Methode auf Timeout-Sekunden. Wenn der Aufruf nicht innerhalb der Timeout-Sekunden abgeschlossen wird, wird ein concurrent.futures.TimeoutError ausgelöst. Sie können int oder float für das Timeout angeben. Wenn kein Timeout angegeben ist oder None ist, ist die Wartezeit unbegrenzt. CanceledError wird ausgelöst, wenn es abgebrochen wird, bevor die Zukunft abgeschlossen ist. Wenn der Aufruf eine Ausnahme auslöst, löst diese Methode dieselbe Ausnahme aus.

Es ist eine Methode, die das Ergebnis zurückgibt, wenn es fertig ist, wartet, wenn es nicht fertig ist, und dann das Ergebnis zurückgibt. Das Ergebnis ist ein Objekt vom Typ "Ergebnis", wie oben gezeigt.

Lesen Sie Result.get_counts

Endlich die letzte Zeile.

print(result.get_counts(qc))

Ich werde schauen.

    def get_counts(self, experiment=None):
        """Get the histogram data of an experiment.
        Args:
            experiment (str or QuantumCircuit or Schedule or int or None): the index of the
                experiment, as specified by ``get_data()``.
        Returns:
            dict[str:int]: a dictionary with the counts for each qubit, with
                the keys containing a string in binary format and separated
                according to the registers in circuit (e.g. ``0100 1110``).
                The string is little-endian (cr[0] on the right hand side).
        Raises:
            QiskitError: if there are no counts for the experiment.
        """
        exp = self._get_experiment(experiment)
        try:
            header = exp.header.to_dict()
        except (AttributeError, QiskitError):  # header is not available
            header = None

        if 'counts' in self.data(experiment).keys():
            return postprocess.format_counts(self.data(experiment)['counts'],
                                             header)
        elif 'statevector' in self.data(experiment).keys():
            vec = postprocess.format_statevector(self.data(experiment)['statevector'])
            return state_to_counts(vec)
        else:
            raise QiskitError('No counts for experiment "{0}"'.format(experiment))

In "_get_experiment" wird die "Erfahrung" entsprechend dem Schaltungsnamen extrahiert. Wenn es diesmal nur ein "Experiment" gibt, können Sie das Argument "Erfahrung" weglassen.

Wenn Sie den Header aus "Experiment" extrahieren können, kennen Sie die Länge des klassischen Registers, sodass Sie das Messergebnis gut auf Null setzen können. In format_counts mache ich eine nette Konvertierung in eine Zeichenkette und packe sie in ein Wörterbuch.

Zusammenfassung

Dieses Mal habe ich mit Qiskit den Verarbeitungsfluss gelesen, mit Ausnahme des Teils, der direkt mit der Quantenberechnung zusammenhängt. Aufgrund wiederholter Änderungen und zukunftssicherer Erweiterbarkeit gab es einige Teile, die sehr schwierige Spezifikationen hatten, aber es wurde gemacht, um eine Menge Dinge tun zu können, und es wurde auch in Transpile und JSON usw. konvertiert. Ich hatte den Eindruck, dass die Teile, mit denen sich Blueqat nicht befasst, ziemlich schwer sind und es eine großartige Lernerfahrung sein wird.

Dies ist das Ende meines Ziels, den gesamten Prozess zu lesen, aber ich werde weiterhin den Qiskit-Quellcode lesen.

Recommended Posts

Qiskit-Quellcode lesen ~ Terra: Backend lesen Get, Call, Get Result
Lesen des Qiskit-Quellcodes ~ Terra: Lesen von der Schaltungserstellung bis zum Hinzufügen von Gates und Messungen
[Python] Lesen Sie den Flask-Quellcode