diff --git a/docs/migration-guides/_toc.json b/docs/migration-guides/_toc.json index 3d80ca99bbb..7e0d19547c0 100644 --- a/docs/migration-guides/_toc.json +++ b/docs/migration-guides/_toc.json @@ -68,6 +68,10 @@ } ] }, + { + "title": "Migrate provider interfaces from backend.run to primitives", + "url": "/migration-guides/external-providers-primitives-v2" + }, { "title": "Qiskit 0.44 changes", "children": [ diff --git a/docs/migration-guides/external-providers-primitives-v2.mdx b/docs/migration-guides/external-providers-primitives-v2.mdx new file mode 100644 index 00000000000..bdf8b110602 --- /dev/null +++ b/docs/migration-guides/external-providers-primitives-v2.mdx @@ -0,0 +1,226 @@ +--- +title: Migrate provider interfaces from backend.run to primitives +description: Migrate to the primitives interface in external providers + +--- + +# Migrate provider interfaces from `backend.run` to primitives + +## Why implement primitives for external providers? + +Similar to the early days of classical computers, when developers had to manipulate CPU registers directly, +the early interface to QPUs simply returned the raw data coming out of the control electronics. +This was not a huge issue when QPUs lived in labs and only allowed direct access by researchers. +When IBM first brought its QPUs to the cloud, we recognized most developers would not and +should not be familiar with distilling such raw data into 0s and 1s. Therefore, +we introduced `backend.run`, our first abstraction for accessing QPUs. This allowed developers +to operate on a data format they were more familiar with and focus on the bigger picture. + +As access to QPUs became more wide spread, and with more quantum algorithms being developed, +we again recognized the need for a higher-level abstraction. This led to the introduction of +the Qiskit [primitives interface](/guides/primitives), which are optimized for two core tasks in quantum algorithm development: +expectation value estimation (Estimator) and circuit sampling (Sampler). The goal is once +again to help developers to focus more on innovation and less on data conversion. + +For backwards compatibility purposes, the `backend.run` interface continues to exist in Qiskit. However, it is deprecated +in Qiskit Runtime, as most of the IBM Quantum users have migrated to V2 primitives due to their improved +usability and efficiency. There is already a collection of migration guides for users to +[transition to the Qiskit Runtime provider](/migration-guides/qiskit-runtime) and +[update their user code to the V2 primitives interface](/migration-guides/v2-primitives) in +time for the upcoming removal of `backend.run` in Qiskit Runtime. + +This migration guide shifts the focus from users and aims to help service providers to migrate from the +`backend.run` interface to primitives, +so their users can also benefit from their improvements. + +Custom primitive implementations can be used to wrap any service provider hardware access function +(for example: `execute_workload(QPU)` or `resource.access()`) or local simulator, +as long as the final inputs and outputs conform to the established standards set by the primitive interfaces. + +## If your provider already implemented `backend.run` + +The Qiskit SDK offers wrappers for `backend.run` that can be easily adapted to a custom primitives workflow +through subclassing; these are the [`BackendEstimatorV2`](/api/qiskit/qiskit.primitives.BackendEstimatorV2) and [`BackendSamplerV2`.](/api/qiskit/qiskit.primitives.BackendSamplerV2) The inputs +to the primitives should follow the Primitive Unified Bloc (PUB) syntax specified in the V2 primitives interface. See the [Overview of PUBs section](/guides/primitive-input-output#pubs) in the Primitive inputs and outputs guide for details. + +An advantage of this strategy is that the wrapper can handle the input and output manipulation, so knowledge of the PUB data +model is not required. However, this might result in a suboptimal runtime, which could be refined through a fully +custom primitives implementation. + +The following snippets show how to create a custom Estimator instance following the strategy described above. +The process is analogous for a custom Sampler, modifying the base class to `BackendSamplerV2`. + +``` python +from qiskit.primitives import BackendEstimatorV2 + +class CustomEstimator(BackendEstimatorV2): + """Estimator primitive for custom provider.""" + + # This line is for type checking purposes. + # We are changing the type of self._backend from qiskit's + # BackendV1/BackendV2 classes to our custom provider resource. + _backend: CustomProviderResource + + def __init__( + self, + backend: CustomProviderResource, + options: dict | None = None, + extra_flag_used_in_estimation: bool = True, + another_extra_flag: bool = False, + ) -> None: + """ + Args: + backend: custom provider resource to evaluate circuits on. + options: options passed to through to the underlying BackendEstimatorV2. + extra_flag_used_in_estimation: if `False`, do this. + another_extra_flag: if `True`, do that, + """ + + # preprocess arguments if necessary according to custom flags + processed_backend = ... + processed_options = ... + + super().__init__( + processed_backend, + options=processed_options, + ) + + @property + def backend(self) -> CustomProviderResource: + """Computing resource used for circuit evaluation.""" + return self._backend +``` + +## If your provider didn't implement `backend.run` or you prefer a fully custom implementation + +If a new provider is developed that doesn't conform to the legacy `backend.run` interface, the pre-packaged +wrapper might not be the optimal route for implementing the primitives. Instead, you should implement a particular +instance of the abstract base primitive interfaces ([BaseEstimatorV2](/api/qiskit/qiskit.primitives.BaseEstimatorV2) or [BaseSamplerV2](/api/qiskit/qiskit.primitives.BaseSamplerV2)). This process requires an +understanding of the PUB data model for input and output handling. + +The following snippet shows a minimal example of an implementation of a custom Sampler primitive following this strategy. +This example has been extracted and generalized from the [`StatevectorSampler`](/api/qiskit/qiskit.primitives.StatevectorSampler) implementation. It has been simplified +for readability. The full original implementation can be found in the [`StatevectorSampler` source code.](https://github.com/Qiskit/qiskit/blob/stable/1.3/qiskit/primitives/statevector_sampler.py) + +``` python +from qiskit.primitives.base import BaseSamplerV2 +from qiskit.primitives.containers import ( + BitArray, + DataBin, + PrimitiveResult, + SamplerPubResult, + SamplerPubLike, +) +from qiskit.primitives.containers.sampler_pub import SamplerPub +from qiskit.primitives.primitive_job import PrimitiveJob +... + +class CustomStatevectorSampler(BaseSamplerV2): + + ... + + def run( + self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None + ) -> PrimitiveJob[PrimitiveResult[SamplerPubResult]]: + ... + coerced_pubs = [SamplerPub.coerce(pub, shots) for pub in pubs] + job = PrimitiveJob(self._run, coerced_pubs) + job._submit() + return job + + def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[SamplerPubResult]: + results = [self._run_pub(pub) for pub in pubs] + return PrimitiveResult(results, metadata={"version": 2}) + + def _run_pub(self, pub: SamplerPub) -> SamplerPubResult: + + # pre-processing of the sampling inputs to fit the required format + circuit, qargs, meas_info = _preprocess_circuit(pub.circuit) + bound_circuits = pub.parameter_values.bind_all(circuit) + arrays = { + item.creg_name: np.zeros( + bound_circuits.shape + (pub.shots, item.num_bytes), dtype=np.uint8 + ) + for item in meas_info + } + for index, bound_circuit in enumerate(bound_circuits): + + # ACCESS PROVIDER RESOURCE HERE + # in this case, we are showing an illustrative implementation + samples_array = ProviderResource.sample(bound_circuit) + + # post-processing of the sampling output to fit the required format + for item in meas_info: + ary = _samples_to_packed_array(samples_array, item.num_bits, item.qreg_indices) + arrays[item.creg_name][index] = ary + + meas = { + item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info + } + return SamplerPubResult( + DataBin(**meas, shape=pub.shape), + metadata={"shots": pub.shots, "circuit_metadata": pub.circuit.metadata}, + ) +``` + +The mechanics to implement a custom Estimator are analogous to those for the Sampler, but might require a different pre- or post-processing +step in the `run` method to extract expectation values from samples. Similar to the Sampler +example, this snippet has been modified and simplified for generality and readability. +The full original implementation can be found in the [`StatevectorEstimator` source code.](https://github.com/Qiskit/qiskit/tree/stable/1.3/qiskit/primitives/statevector_estimator.py#L31-L174) + +``` python +from .base import BaseEstimatorV2 +from .containers import DataBin, EstimatorPubLike, PrimitiveResult, PubResult +from .containers.estimator_pub import EstimatorPub +from .primitive_job import PrimitiveJob +... + +class CustomStatevectorEstimator(BaseEstimatorV2): + + ... + + def run( + self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None + ) -> PrimitiveJob[PrimitiveResult[PubResult]]: + ... + coerced_pubs = [EstimatorPub.coerce(pub, precision) for pub in pubs] + + job = PrimitiveJob(self._run, coerced_pubs) + job._submit() + return job + + def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: + return PrimitiveResult([self._run_pub(pub) for pub in pubs], metadata={"version": 2}) + + def _run_pub(self, pub: EstimatorPub) -> PubResult: + rng = np.random.default_rng(self._seed) + circuit = pub.circuit + observables = pub.observables + parameter_values = pub.parameter_values + precision = pub.precision + bound_circuits = parameter_values.bind_all(circuit) + bc_circuits, bc_obs = np.broadcast_arrays(bound_circuits, observables) + evs = np.zeros_like(bc_circuits, dtype=np.float64) + stds = np.zeros_like(bc_circuits, dtype=np.float64) + + for index in np.ndindex(*bc_circuits.shape): + # pre-processing of the sampling inputs to fit the required format + bound_circuit = bc_circuits[index] + observable = bc_obs[index] + paulis, coeffs = zip(*observable.items()) + obs = SparsePauliOp(paulis, coeffs) + + # ACCESS PROVIDER RESOURCE HERE + # in this case, we are showing an illustrative implementation + samples_array = ProviderResource.sample(bound_circuit, rng, precision) + + # post-processing of the sampling output to extract expectation value + expectation_value = compute_expectation_value(samples_array, obs) + evs[index] = expectation_value + + data = DataBin(evs=evs, stds=stds, shape=evs.shape) + return PubResult( + data, metadata={"target_precision": precision, "circuit_metadata": pub.circuit.metadata} + ) +``` + diff --git a/docs/migration-guides/index.mdx b/docs/migration-guides/index.mdx index 90c3b4acb40..56e2fc9d580 100644 --- a/docs/migration-guides/index.mdx +++ b/docs/migration-guides/index.mdx @@ -11,16 +11,18 @@ These migration guides help you more effectively use Qiskit and Qiskit Runtime: * Migrate to Qiskit 1.0 * [Qiskit 1.0 packaging changes](./qiskit-1.0-installation) * [Qiskit 1.0 feature changes](./qiskit-1.0-features) +* [Migrate from Qiskit Pulse to fractional gates](./pulse-migration) * [Migrate to the Qiskit Runtime V2 primitives](./v2-primitives) -* [Qiskit Runtime execution mode changes](./sessions) -* Migrate to Qiskit Runtime +* [Migrate to local simulators](./local-simulators) +* [Execution mode changes](./sessions) +* Migrate from `backend.run` to primitives * [Overview](./qiskit-runtime) * [Migrate from `qiskit-ibmq-provider`](./qiskit-runtime-from-ibmq-provider) * [Migrate from `qiskit_ibm_provider`](./qiskit-runtime-from-ibm-provider) * [Migrate options](./qiskit-runtime-options) * [Common use cases](./qiskit-runtime-use-case) * [Examples](./qiskit-runtime-examples) -* [Migrate from cloud simulators to local simulators](./local-simulators) +* [Migrate provider interfaces from backend.run to primitives](./external-providers-primitives-v2) * Qiskit 0.44 changes * [`qiskit.algorithms` new interface](./qiskit-algorithms-module) * [`qiskit.opflow` deprecation](./qiskit-opflow-module) diff --git a/docs/open-source/create-a-provider.mdx b/docs/open-source/create-a-provider.mdx index 28a2b8b15c0..ecf75b18349 100644 --- a/docs/open-source/create-a-provider.mdx +++ b/docs/open-source/create-a-provider.mdx @@ -1,14 +1,14 @@ --- title: Create a provider -description: A short guide on integrating Qiskit into an external provider's quantum resources. +description: A short guide on integrating Qiskit into an external provider's quantum resources. --- # Integrate external quantum resources with Qiskit -The Qiskit SDK is built to support third parties in creating external providers of quantum resources. +The Qiskit SDK is built to support third parties in creating external providers of quantum resources. -This means that any organization which develops or deploys quantum compute resources can integrate their services into Qiskit and tap into its userbase. +This means that any organization that develops or deploys quantum compute resources can integrate their services into Qiskit and tap into its userbase. -Doing so requires creating a package which supports requests for quantum compute resources and returns them to the user. +Doing so requires creating a package that supports requests for quantum compute resources and returns them to the user. Additionally, the package must allow users to submit jobs and retrieve their results through an implementation of the `qiskit.primitives` objects. @@ -46,7 +46,7 @@ In addition to providing a service returning hardware configurations, a service To handle job status and results, the Qiskit SDK provides a [`DataBin`](../api/qiskit/qiskit.primitives.DataBin), [`PubResult`](../api/qiskit/qiskit.primitives.PubResult), [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult), and [`BasePrimitiveJob`](../api/qiskit/qiskit.primitives.BasePrimitiveJob) objects should be used. -See the `qiskit.primitives` [API documentation](../api/qiskit/primitives) as well as the reference implementations [`BackendEstimatorV2`](../api/qiskit/qiskit.primitives.BackendEstimatorV2) and [`BackendSampleV2`](../api/qiskit/qiskit.primitives.BackendSampler) for more information. +See the `qiskit.primitives` [API documentation](../api/qiskit/primitives) as well as the reference implementations [`BackendEstimatorV2`](../api/qiskit/qiskit.primitives.BackendEstimatorV2) and [`BackendSampleV2`](../api/qiskit/qiskit.primitives.BackendSampler) for more information. If you created a provider that uses `backend.run`, see [Migrate provider interfaces from backend.run to primitives.](../migration-guides/external-providers-primitives-v2) An example implementation of the Estimator primitive may look like: @@ -105,8 +105,8 @@ class SamplerImplentation(BaseSamplerV2): self._backend = backend self._options = options self._default_shots = 1024 - - @property + + @property def backend(self) -> BackendV2: """ Return the Sampler's backend """ return self._backend @@ -124,5 +124,5 @@ class SamplerImplentation(BaseSamplerV2): """ job = BasePrimitiveJob(pubs, shots) job_with_results = job.submit() - return job_with_results + return job_with_results ``` \ No newline at end of file diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml index 4aa43ee27da..caae97d82c4 100644 --- a/qiskit_bot.yaml +++ b/qiskit_bot.yaml @@ -254,6 +254,10 @@ notifications: - "@javabster" "docs/migration-guides/qiskit-algorithms-module": - "@ElePT" + "docs/migration-guides/external-providers-primitives-v2": + - "@ElePT" + - "@jyu00" + - "@beckykd" "docs/migration-guides/qiskit-opflow-module": - "@ElePT" "docs/migration-guides/qiskit-quantum-instance":