qiskit 1.0.2__cp38-abi3-win32.whl → 1.1.0__cp38-abi3-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qiskit/VERSION.txt +1 -1
- qiskit/__init__.py +27 -16
- qiskit/_accelerate.pyd +0 -0
- qiskit/_numpy_compat.py +73 -0
- qiskit/assembler/__init__.py +5 -10
- qiskit/assembler/disassemble.py +5 -6
- qiskit/circuit/__init__.py +1061 -232
- qiskit/circuit/_classical_resource_map.py +10 -6
- qiskit/circuit/_utils.py +18 -8
- qiskit/circuit/annotated_operation.py +21 -0
- qiskit/circuit/barrier.py +10 -13
- qiskit/circuit/bit.py +0 -1
- qiskit/circuit/classical/__init__.py +2 -2
- qiskit/circuit/classical/expr/__init__.py +39 -5
- qiskit/circuit/classical/expr/constructors.py +84 -1
- qiskit/circuit/classical/expr/expr.py +83 -13
- qiskit/circuit/classical/expr/visitors.py +83 -0
- qiskit/circuit/classical/types/__init__.py +5 -4
- qiskit/circuit/classicalfunction/__init__.py +1 -0
- qiskit/circuit/commutation_checker.py +86 -51
- qiskit/circuit/controlflow/_builder_utils.py +9 -1
- qiskit/circuit/controlflow/break_loop.py +8 -22
- qiskit/circuit/controlflow/builder.py +116 -1
- qiskit/circuit/controlflow/continue_loop.py +8 -22
- qiskit/circuit/controlflow/control_flow.py +47 -8
- qiskit/circuit/controlflow/for_loop.py +8 -23
- qiskit/circuit/controlflow/if_else.py +13 -27
- qiskit/circuit/controlflow/switch_case.py +14 -21
- qiskit/circuit/controlflow/while_loop.py +9 -23
- qiskit/circuit/controlledgate.py +2 -2
- qiskit/circuit/delay.py +7 -5
- qiskit/circuit/gate.py +20 -7
- qiskit/circuit/instruction.py +31 -30
- qiskit/circuit/instructionset.py +9 -22
- qiskit/circuit/library/__init__.py +3 -13
- qiskit/circuit/library/arithmetic/integer_comparator.py +2 -2
- qiskit/circuit/library/arithmetic/quadratic_form.py +3 -2
- qiskit/circuit/library/blueprintcircuit.py +29 -7
- qiskit/circuit/library/data_preparation/state_preparation.py +6 -5
- qiskit/circuit/library/generalized_gates/diagonal.py +5 -4
- qiskit/circuit/library/generalized_gates/isometry.py +51 -254
- qiskit/circuit/library/generalized_gates/pauli.py +2 -2
- qiskit/circuit/library/generalized_gates/permutation.py +4 -1
- qiskit/circuit/library/generalized_gates/rv.py +15 -11
- qiskit/circuit/library/generalized_gates/uc.py +2 -98
- qiskit/circuit/library/generalized_gates/unitary.py +9 -4
- qiskit/circuit/library/hamiltonian_gate.py +11 -5
- qiskit/circuit/library/n_local/efficient_su2.py +5 -5
- qiskit/circuit/library/n_local/n_local.py +100 -49
- qiskit/circuit/library/n_local/two_local.py +3 -59
- qiskit/circuit/library/overlap.py +3 -3
- qiskit/circuit/library/phase_oracle.py +1 -1
- qiskit/circuit/library/quantum_volume.py +39 -38
- qiskit/circuit/library/standard_gates/equivalence_library.py +50 -0
- qiskit/circuit/library/standard_gates/global_phase.py +4 -2
- qiskit/circuit/library/standard_gates/i.py +1 -2
- qiskit/circuit/library/standard_gates/iswap.py +1 -2
- qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +11 -5
- qiskit/circuit/library/standard_gates/p.py +31 -15
- qiskit/circuit/library/standard_gates/r.py +4 -3
- qiskit/circuit/library/standard_gates/rx.py +7 -4
- qiskit/circuit/library/standard_gates/rxx.py +4 -3
- qiskit/circuit/library/standard_gates/ry.py +7 -4
- qiskit/circuit/library/standard_gates/ryy.py +4 -3
- qiskit/circuit/library/standard_gates/rz.py +7 -4
- qiskit/circuit/library/standard_gates/rzx.py +4 -3
- qiskit/circuit/library/standard_gates/rzz.py +4 -3
- qiskit/circuit/library/standard_gates/s.py +4 -8
- qiskit/circuit/library/standard_gates/t.py +2 -4
- qiskit/circuit/library/standard_gates/u.py +16 -11
- qiskit/circuit/library/standard_gates/u1.py +6 -2
- qiskit/circuit/library/standard_gates/u2.py +4 -2
- qiskit/circuit/library/standard_gates/u3.py +9 -5
- qiskit/circuit/library/standard_gates/x.py +22 -11
- qiskit/circuit/library/standard_gates/xx_minus_yy.py +4 -3
- qiskit/circuit/library/standard_gates/xx_plus_yy.py +7 -5
- qiskit/circuit/library/standard_gates/z.py +1 -2
- qiskit/circuit/measure.py +4 -1
- qiskit/circuit/operation.py +13 -8
- qiskit/circuit/parameter.py +11 -6
- qiskit/circuit/quantumcircuit.py +1910 -260
- qiskit/circuit/quantumcircuitdata.py +2 -2
- qiskit/circuit/reset.py +5 -2
- qiskit/circuit/store.py +95 -0
- qiskit/compiler/assembler.py +22 -22
- qiskit/compiler/transpiler.py +63 -112
- qiskit/converters/__init__.py +17 -2
- qiskit/converters/circuit_to_dag.py +7 -0
- qiskit/converters/circuit_to_dagdependency_v2.py +47 -0
- qiskit/converters/circuit_to_gate.py +2 -0
- qiskit/converters/circuit_to_instruction.py +22 -0
- qiskit/converters/dag_to_circuit.py +4 -0
- qiskit/converters/dag_to_dagdependency_v2.py +44 -0
- qiskit/dagcircuit/collect_blocks.py +15 -10
- qiskit/dagcircuit/dagcircuit.py +434 -124
- qiskit/dagcircuit/dagdependency.py +19 -12
- qiskit/dagcircuit/dagdependency_v2.py +641 -0
- qiskit/dagcircuit/dagdepnode.py +19 -16
- qiskit/dagcircuit/dagnode.py +14 -4
- qiskit/passmanager/passmanager.py +11 -11
- qiskit/primitives/__init__.py +22 -12
- qiskit/primitives/backend_estimator.py +3 -5
- qiskit/primitives/backend_estimator_v2.py +410 -0
- qiskit/primitives/backend_sampler_v2.py +287 -0
- qiskit/primitives/base/base_estimator.py +4 -9
- qiskit/primitives/base/base_sampler.py +2 -2
- qiskit/primitives/containers/__init__.py +6 -4
- qiskit/primitives/containers/bit_array.py +293 -2
- qiskit/primitives/containers/data_bin.py +123 -50
- qiskit/primitives/containers/estimator_pub.py +10 -3
- qiskit/primitives/containers/observables_array.py +2 -2
- qiskit/primitives/containers/pub_result.py +1 -1
- qiskit/primitives/containers/sampler_pub.py +19 -3
- qiskit/primitives/containers/sampler_pub_result.py +74 -0
- qiskit/primitives/containers/shape.py +4 -4
- qiskit/primitives/statevector_estimator.py +4 -4
- qiskit/primitives/statevector_sampler.py +7 -12
- qiskit/providers/__init__.py +65 -34
- qiskit/providers/backend.py +2 -2
- qiskit/providers/backend_compat.py +8 -10
- qiskit/providers/basic_provider/__init__.py +2 -23
- qiskit/providers/basic_provider/basic_provider_tools.py +67 -31
- qiskit/providers/basic_provider/basic_simulator.py +81 -21
- qiskit/providers/fake_provider/__init__.py +1 -1
- qiskit/providers/fake_provider/fake_1q.py +1 -1
- qiskit/providers/fake_provider/fake_backend.py +3 -408
- qiskit/providers/fake_provider/generic_backend_v2.py +26 -14
- qiskit/providers/models/__init__.py +2 -2
- qiskit/providers/provider.py +16 -0
- qiskit/pulse/builder.py +4 -1
- qiskit/pulse/parameter_manager.py +60 -4
- qiskit/pulse/schedule.py +29 -13
- qiskit/pulse/utils.py +61 -20
- qiskit/qasm2/__init__.py +1 -5
- qiskit/qasm2/parse.py +1 -4
- qiskit/qasm3/__init__.py +42 -5
- qiskit/qasm3/ast.py +19 -0
- qiskit/qasm3/exporter.py +178 -106
- qiskit/qasm3/printer.py +27 -5
- qiskit/qobj/converters/pulse_instruction.py +6 -6
- qiskit/qpy/__init__.py +299 -67
- qiskit/qpy/binary_io/circuits.py +216 -47
- qiskit/qpy/binary_io/schedules.py +42 -36
- qiskit/qpy/binary_io/value.py +201 -22
- qiskit/qpy/common.py +1 -1
- qiskit/qpy/exceptions.py +20 -0
- qiskit/qpy/formats.py +29 -0
- qiskit/qpy/type_keys.py +21 -0
- qiskit/quantum_info/analysis/distance.py +3 -3
- qiskit/quantum_info/analysis/make_observable.py +2 -1
- qiskit/quantum_info/analysis/z2_symmetries.py +2 -1
- qiskit/quantum_info/operators/channel/chi.py +9 -8
- qiskit/quantum_info/operators/channel/choi.py +10 -9
- qiskit/quantum_info/operators/channel/kraus.py +2 -1
- qiskit/quantum_info/operators/channel/ptm.py +10 -9
- qiskit/quantum_info/operators/channel/quantum_channel.py +2 -1
- qiskit/quantum_info/operators/channel/stinespring.py +2 -1
- qiskit/quantum_info/operators/channel/superop.py +12 -11
- qiskit/quantum_info/operators/channel/transformations.py +12 -11
- qiskit/quantum_info/operators/dihedral/dihedral.py +5 -4
- qiskit/quantum_info/operators/operator.py +43 -30
- qiskit/quantum_info/operators/scalar_op.py +10 -9
- qiskit/quantum_info/operators/symplectic/base_pauli.py +70 -59
- qiskit/quantum_info/operators/symplectic/clifford.py +36 -9
- qiskit/quantum_info/operators/symplectic/pauli.py +53 -6
- qiskit/quantum_info/operators/symplectic/pauli_list.py +36 -14
- qiskit/quantum_info/operators/symplectic/random.py +3 -2
- qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +61 -36
- qiskit/quantum_info/states/densitymatrix.py +13 -13
- qiskit/quantum_info/states/stabilizerstate.py +3 -3
- qiskit/quantum_info/states/statevector.py +14 -13
- qiskit/quantum_info/states/utils.py +5 -3
- qiskit/result/__init__.py +6 -0
- qiskit/result/mitigation/correlated_readout_mitigator.py +3 -2
- qiskit/result/mitigation/local_readout_mitigator.py +2 -1
- qiskit/result/mitigation/utils.py +3 -2
- qiskit/scheduler/__init__.py +10 -1
- qiskit/scheduler/methods/__init__.py +1 -8
- qiskit/synthesis/__init__.py +3 -6
- qiskit/synthesis/discrete_basis/commutator_decompose.py +2 -2
- qiskit/synthesis/evolution/lie_trotter.py +7 -14
- qiskit/synthesis/evolution/qdrift.py +3 -4
- qiskit/synthesis/linear/cnot_synth.py +1 -3
- qiskit/synthesis/linear/linear_circuits_utils.py +1 -1
- qiskit/synthesis/linear_phase/cz_depth_lnn.py +4 -18
- qiskit/synthesis/permutation/__init__.py +1 -0
- qiskit/synthesis/permutation/permutation_reverse_lnn.py +90 -0
- qiskit/synthesis/qft/qft_decompose_lnn.py +2 -6
- qiskit/synthesis/two_qubit/two_qubit_decompose.py +165 -954
- qiskit/synthesis/two_qubit/xx_decompose/circuits.py +13 -12
- qiskit/synthesis/two_qubit/xx_decompose/decomposer.py +7 -1
- qiskit/synthesis/unitary/aqc/__init__.py +1 -1
- qiskit/synthesis/unitary/aqc/cnot_structures.py +2 -1
- qiskit/synthesis/unitary/aqc/fast_gradient/fast_gradient.py +2 -1
- qiskit/synthesis/unitary/qsd.py +3 -2
- qiskit/transpiler/__init__.py +7 -3
- qiskit/transpiler/layout.py +140 -61
- qiskit/transpiler/passes/__init__.py +10 -2
- qiskit/transpiler/passes/basis/basis_translator.py +9 -4
- qiskit/transpiler/passes/basis/unroll_3q_or_more.py +1 -1
- qiskit/transpiler/passes/basis/unroll_custom_definitions.py +1 -1
- qiskit/transpiler/passes/calibration/rzx_builder.py +2 -1
- qiskit/transpiler/passes/layout/apply_layout.py +8 -3
- qiskit/transpiler/passes/layout/sabre_layout.py +15 -3
- qiskit/transpiler/passes/layout/set_layout.py +1 -1
- qiskit/transpiler/passes/optimization/__init__.py +2 -0
- qiskit/transpiler/passes/optimization/commutation_analysis.py +2 -2
- qiskit/transpiler/passes/optimization/commutative_cancellation.py +1 -1
- qiskit/transpiler/passes/optimization/consolidate_blocks.py +1 -1
- qiskit/transpiler/passes/optimization/cx_cancellation.py +10 -0
- qiskit/transpiler/passes/optimization/elide_permutations.py +114 -0
- qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +9 -3
- qiskit/transpiler/passes/optimization/optimize_annotated.py +248 -12
- qiskit/transpiler/passes/optimization/remove_final_reset.py +37 -0
- qiskit/transpiler/passes/optimization/template_matching/forward_match.py +1 -3
- qiskit/transpiler/passes/routing/__init__.py +1 -0
- qiskit/transpiler/passes/routing/basic_swap.py +13 -2
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +8 -1
- qiskit/transpiler/passes/routing/lookahead_swap.py +7 -1
- qiskit/transpiler/passes/routing/sabre_swap.py +10 -6
- qiskit/transpiler/passes/routing/star_prerouting.py +417 -0
- qiskit/transpiler/passes/routing/stochastic_swap.py +24 -8
- qiskit/transpiler/passes/scheduling/__init__.py +1 -1
- qiskit/transpiler/passes/scheduling/alap.py +1 -2
- qiskit/transpiler/passes/scheduling/alignments/align_measures.py +1 -2
- qiskit/transpiler/passes/scheduling/alignments/check_durations.py +9 -6
- qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +8 -0
- qiskit/transpiler/passes/scheduling/alignments/reschedule.py +13 -4
- qiskit/transpiler/passes/scheduling/asap.py +1 -2
- qiskit/transpiler/passes/scheduling/base_scheduler.py +21 -2
- qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +26 -4
- qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +24 -2
- qiskit/transpiler/passes/scheduling/time_unit_conversion.py +28 -4
- qiskit/transpiler/passes/synthesis/aqc_plugin.py +2 -2
- qiskit/transpiler/passes/synthesis/high_level_synthesis.py +120 -13
- qiskit/transpiler/passes/synthesis/unitary_synthesis.py +162 -55
- qiskit/transpiler/passes/utils/gates_basis.py +3 -3
- qiskit/transpiler/passmanager.py +44 -1
- qiskit/transpiler/preset_passmanagers/__init__.py +3 -3
- qiskit/transpiler/preset_passmanagers/builtin_plugins.py +34 -16
- qiskit/transpiler/preset_passmanagers/common.py +4 -6
- qiskit/transpiler/preset_passmanagers/plugin.py +9 -1
- qiskit/utils/__init__.py +3 -2
- qiskit/utils/optionals.py +6 -2
- qiskit/utils/parallel.py +24 -15
- qiskit/visualization/array.py +1 -1
- qiskit/visualization/bloch.py +2 -3
- qiskit/visualization/circuit/matplotlib.py +44 -14
- qiskit/visualization/circuit/text.py +38 -18
- qiskit/visualization/counts_visualization.py +3 -6
- qiskit/visualization/dag_visualization.py +6 -7
- qiskit/visualization/gate_map.py +9 -1
- qiskit/visualization/pulse_v2/interface.py +8 -3
- qiskit/visualization/state_visualization.py +3 -2
- qiskit/visualization/timeline/interface.py +18 -8
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/METADATA +12 -8
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/RECORD +261 -251
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/WHEEL +1 -1
- qiskit/_qasm2.pyd +0 -0
- qiskit/_qasm3.pyd +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/LICENSE.txt +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/entry_points.txt +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
# This code is part of Qiskit.
|
2
|
+
#
|
3
|
+
# (C) Copyright IBM 2024.
|
4
|
+
#
|
5
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
6
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
7
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
8
|
+
#
|
9
|
+
# Any modifications or derivative works of this code must retain this
|
10
|
+
# copyright notice, and modified files need to carry a notice indicating
|
11
|
+
# that they have been altered from the originals.
|
12
|
+
|
13
|
+
"""Sampler V2 implementation for an arbitrary Backend object."""
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
import warnings
|
18
|
+
from collections import defaultdict
|
19
|
+
from dataclasses import dataclass
|
20
|
+
from typing import Iterable
|
21
|
+
|
22
|
+
import numpy as np
|
23
|
+
from numpy.typing import NDArray
|
24
|
+
|
25
|
+
from qiskit.circuit import QuantumCircuit
|
26
|
+
from qiskit.primitives.backend_estimator import _run_circuits
|
27
|
+
from qiskit.primitives.base import BaseSamplerV2
|
28
|
+
from qiskit.primitives.containers import (
|
29
|
+
BitArray,
|
30
|
+
DataBin,
|
31
|
+
PrimitiveResult,
|
32
|
+
SamplerPubLike,
|
33
|
+
SamplerPubResult,
|
34
|
+
)
|
35
|
+
from qiskit.primitives.containers.bit_array import _min_num_bytes
|
36
|
+
from qiskit.primitives.containers.sampler_pub import SamplerPub
|
37
|
+
from qiskit.primitives.primitive_job import PrimitiveJob
|
38
|
+
from qiskit.providers.backend import BackendV1, BackendV2
|
39
|
+
from qiskit.result import Result
|
40
|
+
|
41
|
+
|
42
|
+
@dataclass
|
43
|
+
class Options:
|
44
|
+
"""Options for :class:`~.BackendSamplerV2`"""
|
45
|
+
|
46
|
+
default_shots: int = 1024
|
47
|
+
"""The default shots to use if none are specified in :meth:`~.run`.
|
48
|
+
Default: 1024.
|
49
|
+
"""
|
50
|
+
|
51
|
+
seed_simulator: int | None = None
|
52
|
+
"""The seed to use in the simulator. If None, a random seed will be used.
|
53
|
+
Default: None.
|
54
|
+
"""
|
55
|
+
|
56
|
+
|
57
|
+
@dataclass
|
58
|
+
class _MeasureInfo:
|
59
|
+
creg_name: str
|
60
|
+
num_bits: int
|
61
|
+
num_bytes: int
|
62
|
+
start: int
|
63
|
+
|
64
|
+
|
65
|
+
class BackendSamplerV2(BaseSamplerV2):
|
66
|
+
"""Evaluates bitstrings for provided quantum circuits
|
67
|
+
|
68
|
+
The :class:`~.BackendSamplerV2` class is a generic implementation of the
|
69
|
+
:class:`~.BaseSamplerV2` interface that is used to wrap a :class:`~.BackendV2`
|
70
|
+
(or :class:`~.BackendV1`) object in the class :class:`~.BaseSamplerV2` API. It
|
71
|
+
facilitates using backends that do not provide a native
|
72
|
+
:class:`~.BaseSamplerV2` implementation in places that work with
|
73
|
+
:class:`~.BaseSamplerV2`. However,
|
74
|
+
if you're using a provider that has a native implementation of
|
75
|
+
:class:`~.BaseSamplerV2`, it is a better choice to leverage that native
|
76
|
+
implementation as it will likely include additional optimizations and be
|
77
|
+
a more efficient implementation. The generic nature of this class
|
78
|
+
precludes doing any provider- or backend-specific optimizations.
|
79
|
+
|
80
|
+
This class does not perform any measurement or gate mitigation.
|
81
|
+
|
82
|
+
Each tuple of ``(circuit, <optional> parameter values, <optional> shots)``, called a sampler
|
83
|
+
primitive unified bloc (PUB), produces its own array-valued result. The :meth:`~run` method can
|
84
|
+
be given many pubs at once.
|
85
|
+
|
86
|
+
The options for :class:`~.BackendSamplerV2` consist of the following items.
|
87
|
+
|
88
|
+
* ``default_shots``: The default shots to use if none are specified in :meth:`~run`.
|
89
|
+
Default: 1024.
|
90
|
+
|
91
|
+
* ``seed_simulator``: The seed to use in the simulator. If None, a random seed will be used.
|
92
|
+
Default: None.
|
93
|
+
|
94
|
+
.. note::
|
95
|
+
|
96
|
+
This class requires a backend that supports ``memory`` option.
|
97
|
+
|
98
|
+
"""
|
99
|
+
|
100
|
+
def __init__(
|
101
|
+
self,
|
102
|
+
*,
|
103
|
+
backend: BackendV1 | BackendV2,
|
104
|
+
options: dict | None = None,
|
105
|
+
):
|
106
|
+
"""
|
107
|
+
Args:
|
108
|
+
backend: The backend to run the primitive on.
|
109
|
+
options: The options to control the default shots (``default_shots``) and
|
110
|
+
the random seed for the simulator (``seed_simulator``).
|
111
|
+
"""
|
112
|
+
self._backend = backend
|
113
|
+
self._options = Options(**options) if options else Options()
|
114
|
+
|
115
|
+
@property
|
116
|
+
def backend(self) -> BackendV1 | BackendV2:
|
117
|
+
"""Returns the backend which this sampler object based on."""
|
118
|
+
return self._backend
|
119
|
+
|
120
|
+
@property
|
121
|
+
def options(self) -> Options:
|
122
|
+
"""Return the options"""
|
123
|
+
return self._options
|
124
|
+
|
125
|
+
def run(
|
126
|
+
self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
|
127
|
+
) -> PrimitiveJob[PrimitiveResult[SamplerPubResult]]:
|
128
|
+
if shots is None:
|
129
|
+
shots = self._options.default_shots
|
130
|
+
coerced_pubs = [SamplerPub.coerce(pub, shots) for pub in pubs]
|
131
|
+
self._validate_pubs(coerced_pubs)
|
132
|
+
job = PrimitiveJob(self._run, coerced_pubs)
|
133
|
+
job._submit()
|
134
|
+
return job
|
135
|
+
|
136
|
+
def _validate_pubs(self, pubs: list[SamplerPub]):
|
137
|
+
for i, pub in enumerate(pubs):
|
138
|
+
if len(pub.circuit.cregs) == 0:
|
139
|
+
warnings.warn(
|
140
|
+
f"The {i}-th pub's circuit has no output classical registers and so the result "
|
141
|
+
"will be empty. Did you mean to add measurement instructions?",
|
142
|
+
UserWarning,
|
143
|
+
)
|
144
|
+
|
145
|
+
def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[SamplerPubResult]:
|
146
|
+
pub_dict = defaultdict(list)
|
147
|
+
# consolidate pubs with the same number of shots
|
148
|
+
for i, pub in enumerate(pubs):
|
149
|
+
pub_dict[pub.shots].append(i)
|
150
|
+
|
151
|
+
results = [None] * len(pubs)
|
152
|
+
for shots, lst in pub_dict.items():
|
153
|
+
# run pubs with the same number of shots at once
|
154
|
+
pub_results = self._run_pubs([pubs[i] for i in lst], shots)
|
155
|
+
# reconstruct the result of pubs
|
156
|
+
for i, pub_result in zip(lst, pub_results):
|
157
|
+
results[i] = pub_result
|
158
|
+
return PrimitiveResult(results)
|
159
|
+
|
160
|
+
def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult]:
|
161
|
+
"""Compute results for pubs that all require the same value of ``shots``."""
|
162
|
+
# prepare circuits
|
163
|
+
bound_circuits = [pub.parameter_values.bind_all(pub.circuit) for pub in pubs]
|
164
|
+
flatten_circuits = []
|
165
|
+
for circuits in bound_circuits:
|
166
|
+
flatten_circuits.extend(np.ravel(circuits).tolist())
|
167
|
+
|
168
|
+
# run circuits
|
169
|
+
results, _ = _run_circuits(
|
170
|
+
flatten_circuits,
|
171
|
+
self._backend,
|
172
|
+
memory=True,
|
173
|
+
shots=shots,
|
174
|
+
seed_simulator=self._options.seed_simulator,
|
175
|
+
)
|
176
|
+
result_memory = _prepare_memory(results)
|
177
|
+
|
178
|
+
# pack memory to an ndarray of uint8
|
179
|
+
results = []
|
180
|
+
start = 0
|
181
|
+
for pub, bound in zip(pubs, bound_circuits):
|
182
|
+
meas_info, max_num_bytes = _analyze_circuit(pub.circuit)
|
183
|
+
end = start + bound.size
|
184
|
+
results.append(
|
185
|
+
self._postprocess_pub(
|
186
|
+
result_memory[start:end], shots, bound.shape, meas_info, max_num_bytes
|
187
|
+
)
|
188
|
+
)
|
189
|
+
start = end
|
190
|
+
|
191
|
+
return results
|
192
|
+
|
193
|
+
def _postprocess_pub(
|
194
|
+
self,
|
195
|
+
result_memory: list[list[str]],
|
196
|
+
shots: int,
|
197
|
+
shape: tuple[int, ...],
|
198
|
+
meas_info: list[_MeasureInfo],
|
199
|
+
max_num_bytes: int,
|
200
|
+
) -> SamplerPubResult:
|
201
|
+
"""Converts the memory data into an array of bit arrays with the shape of the pub."""
|
202
|
+
arrays = {
|
203
|
+
item.creg_name: np.zeros(shape + (shots, item.num_bytes), dtype=np.uint8)
|
204
|
+
for item in meas_info
|
205
|
+
}
|
206
|
+
memory_array = _memory_array(result_memory, max_num_bytes)
|
207
|
+
|
208
|
+
for samples, index in zip(memory_array, np.ndindex(*shape)):
|
209
|
+
for item in meas_info:
|
210
|
+
ary = _samples_to_packed_array(samples, item.num_bits, item.start)
|
211
|
+
arrays[item.creg_name][index] = ary
|
212
|
+
|
213
|
+
meas = {
|
214
|
+
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
|
215
|
+
}
|
216
|
+
return SamplerPubResult(DataBin(**meas, shape=shape), metadata={})
|
217
|
+
|
218
|
+
|
219
|
+
def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]:
|
220
|
+
"""Analyzes the information for each creg in a circuit."""
|
221
|
+
meas_info = []
|
222
|
+
max_num_bits = 0
|
223
|
+
for creg in circuit.cregs:
|
224
|
+
name = creg.name
|
225
|
+
num_bits = creg.size
|
226
|
+
if num_bits != 0:
|
227
|
+
start = circuit.find_bit(creg[0]).index
|
228
|
+
else:
|
229
|
+
start = 0
|
230
|
+
meas_info.append(
|
231
|
+
_MeasureInfo(
|
232
|
+
creg_name=name,
|
233
|
+
num_bits=num_bits,
|
234
|
+
num_bytes=_min_num_bytes(num_bits),
|
235
|
+
start=start,
|
236
|
+
)
|
237
|
+
)
|
238
|
+
max_num_bits = max(max_num_bits, start + num_bits)
|
239
|
+
return meas_info, _min_num_bytes(max_num_bits)
|
240
|
+
|
241
|
+
|
242
|
+
def _prepare_memory(results: list[Result]) -> list[list[str]]:
|
243
|
+
"""Joins splitted results if exceeding max_experiments"""
|
244
|
+
lst = []
|
245
|
+
for res in results:
|
246
|
+
for exp in res.results:
|
247
|
+
if hasattr(exp.data, "memory") and exp.data.memory:
|
248
|
+
lst.append(exp.data.memory)
|
249
|
+
else:
|
250
|
+
# no measure in a circuit
|
251
|
+
lst.append(["0x0"] * exp.shots)
|
252
|
+
return lst
|
253
|
+
|
254
|
+
|
255
|
+
def _memory_array(results: list[list[str]], num_bytes: int) -> NDArray[np.uint8]:
|
256
|
+
"""Converts the memory data into an array in an unpacked way."""
|
257
|
+
lst = []
|
258
|
+
for memory in results:
|
259
|
+
if num_bytes > 0:
|
260
|
+
data = b"".join(int(i, 16).to_bytes(num_bytes, "big") for i in memory)
|
261
|
+
data = np.frombuffer(data, dtype=np.uint8).reshape(-1, num_bytes)
|
262
|
+
else:
|
263
|
+
# no measure in a circuit
|
264
|
+
data = np.zeros((len(memory), num_bytes), dtype=np.uint8)
|
265
|
+
lst.append(data)
|
266
|
+
ary = np.asarray(lst)
|
267
|
+
return np.unpackbits(ary, axis=-1, bitorder="big")
|
268
|
+
|
269
|
+
|
270
|
+
def _samples_to_packed_array(
|
271
|
+
samples: NDArray[np.uint8], num_bits: int, start: int
|
272
|
+
) -> NDArray[np.uint8]:
|
273
|
+
"""Converts an unpacked array of the memory data into a packed array."""
|
274
|
+
# samples of `Backend.run(memory=True)` will be the order of
|
275
|
+
# clbit_last, ..., clbit_1, clbit_0
|
276
|
+
# place samples in the order of clbit_start+num_bits-1, ..., clbit_start+1, clbit_start
|
277
|
+
if start == 0:
|
278
|
+
ary = samples[:, -start - num_bits :]
|
279
|
+
else:
|
280
|
+
ary = samples[:, -start - num_bits : -start]
|
281
|
+
# pad 0 in the left to align the number to be mod 8
|
282
|
+
# since np.packbits(bitorder='big') pads 0 to the right.
|
283
|
+
pad_size = -num_bits % 8
|
284
|
+
ary = np.pad(ary, ((0, 0), (pad_size, 0)), constant_values=0)
|
285
|
+
# pack bits in big endian order
|
286
|
+
ary = np.packbits(ary, axis=-1, bitorder="big")
|
287
|
+
return ary
|
@@ -18,8 +18,6 @@ from abc import abstractmethod, ABC
|
|
18
18
|
from collections.abc import Iterable, Sequence
|
19
19
|
from copy import copy
|
20
20
|
from typing import Generic, TypeVar
|
21
|
-
import numpy as np
|
22
|
-
from numpy.typing import NDArray
|
23
21
|
|
24
22
|
from qiskit.circuit import QuantumCircuit
|
25
23
|
from qiskit.providers import JobV1 as Job
|
@@ -27,7 +25,6 @@ from qiskit.quantum_info.operators import SparsePauliOp
|
|
27
25
|
from qiskit.quantum_info.operators.base_operator import BaseOperator
|
28
26
|
|
29
27
|
from ..containers import (
|
30
|
-
make_data_bin,
|
31
28
|
DataBin,
|
32
29
|
EstimatorPubLike,
|
33
30
|
PrimitiveResult,
|
@@ -205,12 +202,10 @@ class BaseEstimatorV2(ABC):
|
|
205
202
|
"""
|
206
203
|
|
207
204
|
@staticmethod
|
208
|
-
def _make_data_bin(
|
209
|
-
#
|
210
|
-
#
|
211
|
-
return
|
212
|
-
(("evs", NDArray[np.float64]), ("stds", NDArray[np.float64])), pub.shape
|
213
|
-
)
|
205
|
+
def _make_data_bin(_: EstimatorPub) -> type[DataBin]:
|
206
|
+
# this method is present for backwards compat. new primitive implementatinos
|
207
|
+
# should avoid it.
|
208
|
+
return DataBin
|
214
209
|
|
215
210
|
@abstractmethod
|
216
211
|
def run(
|
@@ -23,8 +23,8 @@ from qiskit.circuit import QuantumCircuit
|
|
23
23
|
from qiskit.providers import JobV1 as Job
|
24
24
|
|
25
25
|
from ..containers.primitive_result import PrimitiveResult
|
26
|
-
from ..containers.pub_result import PubResult
|
27
26
|
from ..containers.sampler_pub import SamplerPubLike
|
27
|
+
from ..containers.sampler_pub_result import SamplerPubResult
|
28
28
|
from . import validation
|
29
29
|
from .base_primitive import BasePrimitive
|
30
30
|
from .base_primitive_job import BasePrimitiveJob
|
@@ -165,7 +165,7 @@ class BaseSamplerV2(ABC):
|
|
165
165
|
@abstractmethod
|
166
166
|
def run(
|
167
167
|
self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
|
168
|
-
) -> BasePrimitiveJob[PrimitiveResult[
|
168
|
+
) -> BasePrimitiveJob[PrimitiveResult[SamplerPubResult]]:
|
169
169
|
"""Run and collect samples from each pub.
|
170
170
|
|
171
171
|
Args:
|
@@ -15,11 +15,13 @@ Data containers for primitives.
|
|
15
15
|
"""
|
16
16
|
|
17
17
|
|
18
|
+
from .bindings_array import BindingsArrayLike
|
18
19
|
from .bit_array import BitArray
|
19
|
-
from .data_bin import
|
20
|
+
from .data_bin import DataBin, make_data_bin
|
21
|
+
from .estimator_pub import EstimatorPubLike
|
22
|
+
from .observables_array import ObservableLike, ObservablesArrayLike
|
20
23
|
from .primitive_result import PrimitiveResult
|
21
24
|
from .pub_result import PubResult
|
22
|
-
from .estimator_pub import EstimatorPubLike
|
23
25
|
from .sampler_pub import SamplerPubLike
|
24
|
-
from .
|
25
|
-
from .
|
26
|
+
from .sampler_pub_result import SamplerPubResult
|
27
|
+
from .shape import Shaped
|
@@ -19,13 +19,15 @@ from __future__ import annotations
|
|
19
19
|
from collections import defaultdict
|
20
20
|
from functools import partial
|
21
21
|
from itertools import chain, repeat
|
22
|
-
from typing import Callable, Iterable, Literal, Mapping
|
22
|
+
from typing import Callable, Iterable, Literal, Mapping, Sequence
|
23
23
|
|
24
24
|
import numpy as np
|
25
25
|
from numpy.typing import NDArray
|
26
26
|
|
27
|
-
from qiskit.
|
27
|
+
from qiskit.exceptions import QiskitError
|
28
|
+
from qiskit.result import Counts, sampled_expectation_value
|
28
29
|
|
30
|
+
from .observables_array import ObservablesArray, ObservablesArrayLike
|
29
31
|
from .shape import ShapedMixin, ShapeInput, shape_tuple
|
30
32
|
|
31
33
|
# this lookup table tells you how many bits are 1 in each uint8 value
|
@@ -37,6 +39,23 @@ def _min_num_bytes(num_bits: int) -> int:
|
|
37
39
|
return num_bits // 8 + (num_bits % 8 > 0)
|
38
40
|
|
39
41
|
|
42
|
+
def _unpack(bit_array: BitArray) -> NDArray[np.uint8]:
|
43
|
+
arr = np.unpackbits(bit_array.array, axis=-1, bitorder="big")
|
44
|
+
arr = arr[..., -1 : -bit_array.num_bits - 1 : -1]
|
45
|
+
return arr
|
46
|
+
|
47
|
+
|
48
|
+
def _pack(arr: NDArray[np.uint8]) -> tuple[NDArray[np.uint8], int]:
|
49
|
+
arr = arr[..., ::-1]
|
50
|
+
num_bits = arr.shape[-1]
|
51
|
+
pad_size = -num_bits % 8
|
52
|
+
if pad_size > 0:
|
53
|
+
pad_width = [(0, 0)] * (arr.ndim - 1) + [(pad_size, 0)]
|
54
|
+
arr = np.pad(arr, pad_width, constant_values=0)
|
55
|
+
arr = np.packbits(arr, axis=-1, bitorder="big")
|
56
|
+
return arr, num_bits
|
57
|
+
|
58
|
+
|
40
59
|
class BitArray(ShapedMixin):
|
41
60
|
"""Stores an array of bit values.
|
42
61
|
|
@@ -110,6 +129,14 @@ class BitArray(ShapedMixin):
|
|
110
129
|
desc = f"<shape={self.shape}, num_shots={self.num_shots}, num_bits={self.num_bits}>"
|
111
130
|
return f"BitArray({desc})"
|
112
131
|
|
132
|
+
def __getitem__(self, indices):
|
133
|
+
"""Slices the array along an existing axis of the array."""
|
134
|
+
if isinstance(indices, tuple) and len(indices) >= self.ndim + 2:
|
135
|
+
raise ValueError(
|
136
|
+
"BitArrays cannot be sliced along the bits axis, see slice_bits() instead."
|
137
|
+
)
|
138
|
+
return BitArray(self._array[indices], self.num_bits)
|
139
|
+
|
113
140
|
@property
|
114
141
|
def array(self) -> NDArray[np.uint8]:
|
115
142
|
"""The raw NumPy array of data."""
|
@@ -347,3 +374,267 @@ class BitArray(ShapedMixin):
|
|
347
374
|
else:
|
348
375
|
raise ValueError("Cannot change the size of the array.")
|
349
376
|
return BitArray(self._array.reshape(shape), self.num_bits)
|
377
|
+
|
378
|
+
def transpose(self, *axes) -> "BitArray":
|
379
|
+
"""Return a bit array with axes transposed.
|
380
|
+
|
381
|
+
Args:
|
382
|
+
axes: None, tuple of ints or n ints. See `ndarray.transpose
|
383
|
+
<https://numpy.org/doc/stable/reference/generated/
|
384
|
+
numpy.ndarray.transpose.html#numpy.ndarray.transpose>`_
|
385
|
+
for the details.
|
386
|
+
|
387
|
+
Returns:
|
388
|
+
BitArray: A bit array with axes permuted.
|
389
|
+
|
390
|
+
Raises:
|
391
|
+
ValueError: If ``axes`` don't match this bit array.
|
392
|
+
ValueError: If ``axes`` includes any indices that are out of bounds.
|
393
|
+
"""
|
394
|
+
if len(axes) == 0:
|
395
|
+
axes = tuple(reversed(range(self.ndim)))
|
396
|
+
if len(axes) == 1 and isinstance(axes[0], Sequence):
|
397
|
+
axes = axes[0]
|
398
|
+
if len(axes) != self.ndim:
|
399
|
+
raise ValueError("axes don't match bit array")
|
400
|
+
for i in axes:
|
401
|
+
if i >= self.ndim or self.ndim + i < 0:
|
402
|
+
raise ValueError(
|
403
|
+
f"axis {i} is out of bounds for bit array of dimension {self.ndim}."
|
404
|
+
)
|
405
|
+
axes = tuple(i if i >= 0 else self.ndim + i for i in axes) + (-2, -1)
|
406
|
+
return BitArray(self._array.transpose(axes), self.num_bits)
|
407
|
+
|
408
|
+
def slice_bits(self, indices: int | Sequence[int]) -> "BitArray":
|
409
|
+
"""Return a bit array sliced along the bit axis of some indices of interest.
|
410
|
+
|
411
|
+
.. note::
|
412
|
+
|
413
|
+
The convention used by this method is that the index ``0`` corresponds to
|
414
|
+
the least-significant bit in the :attr:`~array`, or equivalently
|
415
|
+
the right-most bitstring entry as returned by
|
416
|
+
:meth:`~get_counts` or :meth:`~get_bitstrings`, etc.
|
417
|
+
|
418
|
+
If this bit array was produced by a sampler, then an index ``i`` corresponds to the
|
419
|
+
:class:`~.ClassicalRegister` location ``creg[i]``.
|
420
|
+
|
421
|
+
Args:
|
422
|
+
indices: The bit positions of interest to slice along.
|
423
|
+
|
424
|
+
Returns:
|
425
|
+
A bit array sliced along the bit axis.
|
426
|
+
|
427
|
+
Raises:
|
428
|
+
ValueError: If there are any invalid indices of the bit axis.
|
429
|
+
"""
|
430
|
+
if isinstance(indices, int):
|
431
|
+
indices = (indices,)
|
432
|
+
for index in indices:
|
433
|
+
if index < 0 or index >= self.num_bits:
|
434
|
+
raise ValueError(
|
435
|
+
f"index {index} is out of bounds for the number of bits {self.num_bits}."
|
436
|
+
)
|
437
|
+
# This implementation introduces a temporary 8x memory overhead due to bit
|
438
|
+
# unpacking. This could be fixed using bitwise functions, at the expense of a
|
439
|
+
# more complicated implementation.
|
440
|
+
arr = _unpack(self)
|
441
|
+
arr = arr[..., indices]
|
442
|
+
arr, num_bits = _pack(arr)
|
443
|
+
return BitArray(arr, num_bits)
|
444
|
+
|
445
|
+
def slice_shots(self, indices: int | Sequence[int]) -> "BitArray":
|
446
|
+
"""Return a bit array sliced along the shots axis of some indices of interest.
|
447
|
+
|
448
|
+
Args:
|
449
|
+
indices: The shots positions of interest to slice along.
|
450
|
+
|
451
|
+
Returns:
|
452
|
+
A bit array sliced along the shots axis.
|
453
|
+
|
454
|
+
Raises:
|
455
|
+
ValueError: If there are any invalid indices of the shots axis.
|
456
|
+
"""
|
457
|
+
if isinstance(indices, int):
|
458
|
+
indices = (indices,)
|
459
|
+
for index in indices:
|
460
|
+
if index < 0 or index >= self.num_shots:
|
461
|
+
raise ValueError(
|
462
|
+
f"index {index} is out of bounds for the number of shots {self.num_shots}."
|
463
|
+
)
|
464
|
+
arr = self._array
|
465
|
+
arr = arr[..., indices, :]
|
466
|
+
return BitArray(arr, self.num_bits)
|
467
|
+
|
468
|
+
def expectation_values(self, observables: ObservablesArrayLike) -> NDArray[np.float64]:
|
469
|
+
"""Compute the expectation values of the provided observables, broadcasted against
|
470
|
+
this bit array.
|
471
|
+
|
472
|
+
.. note::
|
473
|
+
|
474
|
+
This method returns the real part of the expectation value even if
|
475
|
+
the operator has complex coefficients due to the specification of
|
476
|
+
:func:`~.sampled_expectation_value`.
|
477
|
+
|
478
|
+
Args:
|
479
|
+
observables: The observable(s) to take the expectation value of.
|
480
|
+
Must have a shape broadcastable with with this bit array and
|
481
|
+
the same number of qubits as the number of bits of this bit array.
|
482
|
+
The observables must be diagonal (I, Z, 0 or 1) too.
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
An array of expectation values whose shape is the broadcast shape of ``observables``
|
486
|
+
and this bit array.
|
487
|
+
|
488
|
+
Raises:
|
489
|
+
ValueError: If the provided observables does not have a shape broadcastable with
|
490
|
+
this bit array.
|
491
|
+
ValueError: If the provided observables does not have the same number of qubits as
|
492
|
+
the number of bits of this bit array.
|
493
|
+
ValueError: If the provided observables are not diagonal.
|
494
|
+
"""
|
495
|
+
observables = ObservablesArray.coerce(observables)
|
496
|
+
arr_indices = np.fromiter(np.ndindex(self.shape), dtype=object).reshape(self.shape)
|
497
|
+
bc_indices, bc_obs = np.broadcast_arrays(arr_indices, observables)
|
498
|
+
counts = {}
|
499
|
+
arr = np.zeros_like(bc_indices, dtype=float)
|
500
|
+
for index in np.ndindex(bc_indices.shape):
|
501
|
+
loc = bc_indices[index]
|
502
|
+
for pauli, coeff in bc_obs[index].items():
|
503
|
+
if loc not in counts:
|
504
|
+
counts[loc] = self.get_counts(loc)
|
505
|
+
try:
|
506
|
+
expval = sampled_expectation_value(counts[loc], pauli)
|
507
|
+
except QiskitError as ex:
|
508
|
+
raise ValueError(ex.message) from ex
|
509
|
+
arr[index] += expval * coeff
|
510
|
+
return arr
|
511
|
+
|
512
|
+
@staticmethod
|
513
|
+
def concatenate(bit_arrays: Sequence[BitArray], axis: int = 0) -> BitArray:
|
514
|
+
"""Join a sequence of bit arrays along an existing axis.
|
515
|
+
|
516
|
+
Args:
|
517
|
+
bit_arrays: The bit arrays must have (1) the same number of bits,
|
518
|
+
(2) the same number of shots, and
|
519
|
+
(3) the same shape, except in the dimension corresponding to axis
|
520
|
+
(the first, by default).
|
521
|
+
axis: The axis along which the arrays will be joined. Default is 0.
|
522
|
+
|
523
|
+
Returns:
|
524
|
+
The concatenated bit array.
|
525
|
+
|
526
|
+
Raises:
|
527
|
+
ValueError: If the sequence of bit arrays is empty.
|
528
|
+
ValueError: If any bit arrays has a different number of bits.
|
529
|
+
ValueError: If any bit arrays has a different number of shots.
|
530
|
+
ValueError: If any bit arrays has a different number of dimensions.
|
531
|
+
"""
|
532
|
+
if len(bit_arrays) == 0:
|
533
|
+
raise ValueError("Need at least one bit array to concatenate")
|
534
|
+
num_bits = bit_arrays[0].num_bits
|
535
|
+
num_shots = bit_arrays[0].num_shots
|
536
|
+
ndim = bit_arrays[0].ndim
|
537
|
+
if ndim == 0:
|
538
|
+
raise ValueError("Zero-dimensional bit arrays cannot be concatenated")
|
539
|
+
for i, ba in enumerate(bit_arrays):
|
540
|
+
if ba.num_bits != num_bits:
|
541
|
+
raise ValueError(
|
542
|
+
"All bit arrays must have same number of bits, "
|
543
|
+
f"but the bit array at index 0 has {num_bits} bits "
|
544
|
+
f"and the bit array at index {i} has {ba.num_bits} bits."
|
545
|
+
)
|
546
|
+
if ba.num_shots != num_shots:
|
547
|
+
raise ValueError(
|
548
|
+
"All bit arrays must have same number of shots, "
|
549
|
+
f"but the bit array at index 0 has {num_shots} shots "
|
550
|
+
f"and the bit array at index {i} has {ba.num_shots} shots."
|
551
|
+
)
|
552
|
+
if ba.ndim != ndim:
|
553
|
+
raise ValueError(
|
554
|
+
"All bit arrays must have same number of dimensions, "
|
555
|
+
f"but the bit array at index 0 has {ndim} dimension(s) "
|
556
|
+
f"and the bit array at index {i} has {ba.ndim} dimension(s)."
|
557
|
+
)
|
558
|
+
if axis < 0 or axis >= ndim:
|
559
|
+
raise ValueError(f"axis {axis} is out of bounds for bit array of dimension {ndim}.")
|
560
|
+
data = np.concatenate([ba.array for ba in bit_arrays], axis=axis)
|
561
|
+
return BitArray(data, num_bits)
|
562
|
+
|
563
|
+
@staticmethod
|
564
|
+
def concatenate_shots(bit_arrays: Sequence[BitArray]) -> BitArray:
|
565
|
+
"""Join a sequence of bit arrays along the shots axis.
|
566
|
+
|
567
|
+
Args:
|
568
|
+
bit_arrays: The bit arrays must have (1) the same number of bits,
|
569
|
+
and (2) the same shape.
|
570
|
+
|
571
|
+
Returns:
|
572
|
+
The stacked bit array.
|
573
|
+
|
574
|
+
Raises:
|
575
|
+
ValueError: If the sequence of bit arrays is empty.
|
576
|
+
ValueError: If any bit arrays has a different number of bits.
|
577
|
+
ValueError: If any bit arrays has a different shape.
|
578
|
+
"""
|
579
|
+
if len(bit_arrays) == 0:
|
580
|
+
raise ValueError("Need at least one bit array to stack")
|
581
|
+
num_bits = bit_arrays[0].num_bits
|
582
|
+
shape = bit_arrays[0].shape
|
583
|
+
for i, ba in enumerate(bit_arrays):
|
584
|
+
if ba.num_bits != num_bits:
|
585
|
+
raise ValueError(
|
586
|
+
"All bit arrays must have same number of bits, "
|
587
|
+
f"but the bit array at index 0 has {num_bits} bits "
|
588
|
+
f"and the bit array at index {i} has {ba.num_bits} bits."
|
589
|
+
)
|
590
|
+
if ba.shape != shape:
|
591
|
+
raise ValueError(
|
592
|
+
"All bit arrays must have same shape, "
|
593
|
+
f"but the bit array at index 0 has shape {shape} "
|
594
|
+
f"and the bit array at index {i} has shape {ba.shape}."
|
595
|
+
)
|
596
|
+
data = np.concatenate([ba.array for ba in bit_arrays], axis=-2)
|
597
|
+
return BitArray(data, num_bits)
|
598
|
+
|
599
|
+
@staticmethod
|
600
|
+
def concatenate_bits(bit_arrays: Sequence[BitArray]) -> BitArray:
|
601
|
+
"""Join a sequence of bit arrays along the bits axis.
|
602
|
+
|
603
|
+
.. note::
|
604
|
+
This method is equivalent to per-shot bitstring concatenation.
|
605
|
+
|
606
|
+
Args:
|
607
|
+
bit_arrays: Bit arrays that have (1) the same number of shots,
|
608
|
+
and (2) the same shape.
|
609
|
+
|
610
|
+
Returns:
|
611
|
+
The stacked bit array.
|
612
|
+
|
613
|
+
Raises:
|
614
|
+
ValueError: If the sequence of bit arrays is empty.
|
615
|
+
ValueError: If any bit arrays has a different number of shots.
|
616
|
+
ValueError: If any bit arrays has a different shape.
|
617
|
+
"""
|
618
|
+
if len(bit_arrays) == 0:
|
619
|
+
raise ValueError("Need at least one bit array to stack")
|
620
|
+
num_shots = bit_arrays[0].num_shots
|
621
|
+
shape = bit_arrays[0].shape
|
622
|
+
for i, ba in enumerate(bit_arrays):
|
623
|
+
if ba.num_shots != num_shots:
|
624
|
+
raise ValueError(
|
625
|
+
"All bit arrays must have same number of shots, "
|
626
|
+
f"but the bit array at index 0 has {num_shots} shots "
|
627
|
+
f"and the bit array at index {i} has {ba.num_shots} shots."
|
628
|
+
)
|
629
|
+
if ba.shape != shape:
|
630
|
+
raise ValueError(
|
631
|
+
"All bit arrays must have same shape, "
|
632
|
+
f"but the bit array at index 0 has shape {shape} "
|
633
|
+
f"and the bit array at index {i} has shape {ba.shape}."
|
634
|
+
)
|
635
|
+
# This implementation introduces a temporary 8x memory overhead due to bit
|
636
|
+
# unpacking. This could be fixed using bitwise functions, at the expense of a
|
637
|
+
# more complicated implementation.
|
638
|
+
data = np.concatenate([_unpack(ba) for ba in bit_arrays], axis=-1)
|
639
|
+
data, num_bits = _pack(data)
|
640
|
+
return BitArray(data, num_bits)
|