-
Couldn't load subscription status.
- Fork 2
Refactoring API structure #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
05ddfc9
cfc7d3d
cc96047
94f5506
edd098b
d704a07
7be6452
4d695d6
edf5f22
66154e7
5621900
9ebab22
ddfaff6
ad49e67
c4748e7
9d9adf1
b153822
409ae3c
9cade0e
5396ce4
fcbfe34
f4353bd
d2b6035
b655ce0
aa713a2
8b533a1
71f4359
c1074ae
c6f94d1
ae3882f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| from __future__ import annotations | ||
| from typing import TYPE_CHECKING | ||
| import logging | ||
|
|
||
| from qiskit_aer import AerSimulator | ||
| from qiskit_aer.noise import NoiseModel | ||
| from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager | ||
| from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService | ||
|
|
||
| from graphix_ibmq.compile_options import IBMQCompileOptions | ||
| from graphix_ibmq.compiler import IBMQPatternCompiler, IBMQCompiledCircuit | ||
| from graphix_ibmq.job import IBMQJob | ||
|
|
||
| if TYPE_CHECKING: | ||
| from graphix.pattern import Pattern | ||
| from qiskit.providers.backend import BackendV2, Backend | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class IBMQBackend: | ||
| """ | ||
| Manages compilation and execution on IBMQ simulators or hardware. | ||
| This class configures the execution target and provides methods to compile | ||
| a graphix Pattern and submit it as a job. Instances should be created using | ||
| the `from_simulator` or `from_hardware` classmethods. | ||
| """ | ||
|
|
||
| def __init__(self, backend: Backend | None = None, options: IBMQCompileOptions | None = None) -> None: | ||
d1ssk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if backend is None or options is None: | ||
| raise TypeError( | ||
| "IBMQBackend cannot be instantiated directly. " | ||
| "Please use the classmethods `IBMQBackend.from_simulator()` " | ||
| "or `IBMQBackend.from_hardware()`." | ||
| ) | ||
| self._backend: Backend = backend | ||
| self._options: IBMQCompileOptions = options | ||
|
|
||
| @classmethod | ||
| def from_simulator( | ||
| cls, | ||
| noise_model: NoiseModel | None = None, | ||
| from_backend: BackendV2 | None = None, | ||
| options: IBMQCompileOptions | None = None, | ||
| ) -> IBMQBackend: | ||
| """Creates an instance with a local Aer simulator as the backend. | ||
| Parameters | ||
| ---------- | ||
| noise_model : NoiseModel, optional | ||
| A custom noise model for the simulation. | ||
| from_backend : BackendV2, optional | ||
| A hardware backend to base the noise model on. | ||
| Ignored if `noise_model` is provided. | ||
| options : IBMQCompileOptions, optional | ||
| Compilation and execution options. | ||
| """ | ||
| if noise_model is None and from_backend is not None: | ||
| noise_model = NoiseModel.from_backend(from_backend) | ||
|
|
||
| aer_backend = AerSimulator(noise_model=noise_model) | ||
| compile_options = options if options is not None else IBMQCompileOptions() | ||
|
|
||
| logger.info("Backend set to local AerSimulator.") | ||
| return cls(backend=aer_backend, options=compile_options) | ||
|
|
||
| @classmethod | ||
| def from_hardware( | ||
| cls, | ||
| name: str | None = None, | ||
| least_busy: bool = False, | ||
d1ssk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| min_qubits: int = 1, | ||
| options: IBMQCompileOptions | None = None, | ||
| ) -> IBMQBackend: | ||
| """Creates an instance with a real IBM Quantum hardware device as the backend. | ||
| Parameters | ||
| ---------- | ||
| name : str, optional | ||
| The specific name of the device (e.g., 'ibm_brisbane'). | ||
| least_busy : bool | ||
| If True, selects the least busy device meeting the criteria. | ||
| min_qubits : int | ||
| The minimum number of qubits required. | ||
| options : IBMQCompileOptions, optional | ||
| Compilation and execution options. | ||
| """ | ||
| service = QiskitRuntimeService() | ||
| if name: | ||
| hw_backend = service.backend(name) | ||
| else: | ||
| hw_backend = service.least_busy(min_num_qubits=min_qubits, operational=True) | ||
|
|
||
| compile_options = options if options is not None else IBMQCompileOptions() | ||
|
|
||
| logger.info("Selected hardware backend: %s", hw_backend.name) | ||
| return cls(backend=hw_backend, options=compile_options) | ||
|
|
||
| @staticmethod | ||
| def compile(pattern: Pattern, save_statevector: bool = False) -> IBMQCompiledCircuit: | ||
| """Compiles a graphix pattern into a Qiskit QuantumCircuit. | ||
| This method is provided as a staticmethod because it does not depend | ||
| on the backend's state. | ||
| Parameters | ||
| ---------- | ||
| pattern : Pattern | ||
| The graphix pattern to compile. | ||
| save_statevector : bool | ||
| If True, saves the statevector before the final measurement. | ||
| Returns | ||
| ------- | ||
| IBMQCompiledCircuit | ||
| An object containing the compiled circuit and related metadata. | ||
| """ | ||
| compiler = IBMQPatternCompiler(pattern) | ||
| return compiler.compile(save_statevector=save_statevector) | ||
|
|
||
| def submit_job(self, compiled_circuit: IBMQCompiledCircuit, shots: int = 1024) -> IBMQJob: | ||
| """ | ||
| Submits the compiled circuit to the configured backend for execution. | ||
| Parameters | ||
| ---------- | ||
| compiled_circuit : IBMQCompiledCircuit | ||
| The compiled circuit object from the `compile` method. | ||
| shots : int, optional | ||
| The number of execution shots. Defaults to 1024. | ||
| Returns | ||
| ------- | ||
| IBMQJob | ||
| A job object to monitor execution and retrieve results. | ||
| """ | ||
| pass_manager = generate_preset_pass_manager( | ||
| backend=self._backend, | ||
| optimization_level=self._options.optimization_level, | ||
| ) | ||
| transpiled_circuit = pass_manager.run(compiled_circuit.circuit) | ||
|
|
||
| sampler = Sampler(mode=self._backend) | ||
| job = sampler.run([transpiled_circuit], shots=shots) | ||
|
|
||
| return IBMQJob(job, compiled_circuit) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
d1ssk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| @dataclass | ||
| class IBMQCompileOptions: | ||
| """Compilation options specific to IBMQ backends. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| optimization_level : int | ||
| Optimization level for Qiskit transpiler (0 to 3). | ||
| save_statevector : bool | ||
| Whether to save the statevector before measurement (for debugging/testing). | ||
| layout_method : str | ||
| Qubit layout method used by the transpiler (for future use). | ||
| """ | ||
|
|
||
d1ssk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| optimization_level: int = 3 | ||
| save_statevector: bool = False | ||
| layout_method: str = "trivial" | ||
|
|
||
| def __repr__(self) -> str: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is automatically implemented by |
||
| """Return a string representation of the compilation options.""" | ||
| return ( | ||
| f"IBMQCompileOptions(optimization_level={self.optimization_level}, " | ||
| f"save_statevector={self.save_statevector}, " | ||
| f"layout_method='{self.layout_method}')" | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These versions of Python are not really used: regardless of the version of Python we test here,
toxinstalls and tests Python3.9,3.10, and3.11in its own environments. That means we run the exact same set of tests four times, and each time we test the three versions of Python listed intox.ini. We probably want to test only one version of Python at this level (and probably even without explicitly specifying a particular version), and lettoxrun the tests in its multiple environments.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comment, but actually I don't think the current setup runs every tox env four times. Each github actions job only invokes its corresponding tox environment (e.g. the 3.10 runner only runs
py310, the 3.11 runner only runspy311, etc.), although runningtoxin local environment indeed execute tests in all the versions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, sorry: I was mistaken, you're right. That is the purpose of the
[gh-actions]section intox.ini. Then everything is fine!