Skip to content
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

Update BaseEstimator import to support qiskit 2.0 #2327

Merged
merged 33 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e3d9383
Use versioned base class for BaseEstimatorV1 instead of deprecated un…
ElePT Mar 5, 2025
cf942c3
Vendor removed V1 utils
ElePT Mar 11, 2025
d51aee6
Fix black
ElePT Mar 11, 2025
e877fc0
Replace bit with qubit
ElePT Mar 11, 2025
e2d93d5
Remove assemble test, as assemble was deprecated in 1.4 and removed i…
ElePT Mar 11, 2025
1695710
Merge branch 'main' of https://github.com/Qiskit/qiskit-aer into upda…
ElePT Mar 11, 2025
9fc42d1
Treat header as dict
ElePT Mar 11, 2025
6da91ff
Handle header conditionally
ElePT Mar 11, 2025
2f4d880
c_if is removed in 2.0, keep functionality for compatibility with 1.4…
ElePT Mar 11, 2025
69c8825
Fix access to header in tests
ElePT Mar 11, 2025
e36e160
Remove use of instruction_durations in transpile
ElePT Mar 11, 2025
18344cb
Attempt at fixing custom gate mapping. Not sure if gate = None is acc…
ElePT Mar 11, 2025
f6546e8
IfElseOp is not supported by statevector simulator. Reverted uni test…
ElePT Mar 11, 2025
2d0b1bb
Remove wrong import
ElePT Mar 11, 2025
983bafb
Fix failed target construction
ElePT Mar 12, 2025
c5865df
Only run tests with c_if with qiskit<2
ElePT Mar 12, 2025
17f9c34
Address leftover custom basis gates in transpiler
ElePT Mar 12, 2025
fabca5e
Attempt at addressing .duration removal
ElePT Mar 12, 2025
f7617b8
Add unittest import missing in previous commit
ElePT Mar 12, 2025
9cf7191
Use new target arg in tests for relaxation noise pass
ElePT Mar 12, 2025
07b652f
Fix lint
ElePT Mar 12, 2025
edc89aa
Don't use target with backend props
ElePT Mar 12, 2025
bb1869a
Attempt at fixing relaxation pass
ElePT Mar 12, 2025
a643439
use hashable type
ElePT Mar 12, 2025
0d57cfd
Fix issue with pickle/dill (temporary skip of fractional gates)
ElePT Mar 12, 2025
4d9cdd8
Add missing c_if skip
ElePT Mar 12, 2025
e38f0d0
Skip transpilation to avoid qiskit bug in test
ElePT Mar 12, 2025
41ac5a2
Skipping transpilation affects results
ElePT Mar 12, 2025
db5a011
Update truncation test. This test was relying on the transpiler blowi…
ElePT Mar 12, 2025
8f78f29
Apply suggestion to use shots None
ElePT Mar 12, 2025
5375346
Get rid of shot noise
ElePT Mar 12, 2025
7d554c3
Improve truncation test
ElePT Mar 13, 2025
4fe916c
Assert number of active qubits
ElePT Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit_aer/noise/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ def from_backend(
dt=dt,
op_types=Delay,
excited_state_populations=excited_state_populations,
target=target,
)
noise_model._custom_noise_passes.append(delay_pass)
except ValueError:
Expand Down
69 changes: 52 additions & 17 deletions qiskit_aer/noise/passes/relaxation_noise_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import numpy as np

from qiskit.circuit import Instruction, QuantumCircuit
from qiskit.transpiler import Target
from qiskit.utils.units import apply_prefix
from .local_noise_pass import LocalNoisePass
from ..errors.standard_errors import thermal_relaxation_error
Expand All @@ -34,6 +35,7 @@ def __init__(
dt: Optional[float] = None,
op_types: Optional[Union[type, Sequence[type]]] = None,
excited_state_populations: Optional[List[float]] = None,
target: Target = None,
):
"""Initialize RelaxationNoisePass.

Expand All @@ -47,6 +49,7 @@ def __init__(
excited_state_populations: Optional, list of excited state populations
for each qubit at thermal equilibrium. If not supplied or obtained
from the backend this will be set to 0 for each qubit.
target: Optional, target instance with instruction duration information.
"""
self._t1s = np.asarray(t1s)
self._t2s = np.asarray(t2s)
Expand All @@ -55,29 +58,61 @@ def __init__(
else:
self._p1s = np.zeros(len(t1s))
self._dt = dt
self._target = target
super().__init__(self._thermal_relaxation_error, op_types=op_types, method="append")

def _thermal_relaxation_error(self, op: Instruction, qubits: Sequence[int]):
"""Return thermal relaxation error on each operand qubit"""
if not op.duration:
if op.duration is None:
warnings.warn(
"RelaxationNoisePass ignores instructions without duration,"
" you may need to schedule circuit in advance.",
UserWarning,
)
return None

# Convert op duration to seconds
if op.unit == "dt":
if self._dt is None:
raise NoiseError(
"RelaxationNoisePass cannot apply noise to a 'dt' unit duration"
" without a dt time set."
)
duration = op.duration * self._dt
duration = None

if self._target is not None:
if op.name == "delay":
duration = op.duration
if duration is not None:
# Convert op duration to seconds
if op.unit == "dt":
if self._dt is None:
raise NoiseError(
"RelaxationNoisePass cannot apply noise to a 'dt' unit duration"
" without a dt time set."
)
duration = op.duration * self._dt
else:
duration = apply_prefix(op.duration, op.unit)
else:
op_props = self._target.get(op.name)
if op_props is not None:
inst_props = op_props.get(tuple(qubits))
if inst_props is not None:
# get duration in seconds
duration = getattr(inst_props, "duration")
else:
duration = apply_prefix(op.duration, op.unit)
try:
duration = op.duration
if duration is not None:
# Convert op duration to seconds
if op.unit == "dt":
if self._dt is None:
raise NoiseError(
"RelaxationNoisePass cannot apply noise to a 'dt' unit duration"
" without a dt time set."
)
duration = op.duration * self._dt
else:
duration = apply_prefix(op.duration, op.unit)

except AttributeError:
duration = None

if duration is None:
warnings.warn(
r"Instruction duration not found for {op.name}. RelaxationNoisePass ignores "
"instructions without duration. "
"To avoid this warning, provide the corresponding duration information via `target`.",
UserWarning,
)
return None

t1s = self._t1s[qubits]
t2s = self._t2s[qubits]
Expand Down
55 changes: 50 additions & 5 deletions qiskit_aer/primitives/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
import numpy as np
from qiskit.circuit import ParameterExpression, QuantumCircuit
from qiskit.compiler import transpile
from qiskit.primitives import BaseEstimator, EstimatorResult
from qiskit.primitives import BaseEstimatorV1, EstimatorResult
from qiskit.primitives.primitive_job import PrimitiveJob
from qiskit.primitives.utils import _circuit_key, _observable_key, init_observable
from qiskit.providers import Options
from qiskit.quantum_info import Pauli, PauliList
from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.symplectic.base_pauli import BasePauli
from qiskit.result.models import ExperimentResult
from qiskit.transpiler import CouplingMap, PassManager
from qiskit.transpiler.passes import (
Expand All @@ -41,9 +41,49 @@
from qiskit.utils import deprecate_func

from .. import AerError, AerSimulator
from .sampler import _circuit_key


class Estimator(BaseEstimator):
def init_observable(observable: BaseOperator | str) -> SparsePauliOp:
"""Initialize observable by converting the input to a :class:`~qiskit.quantum_info.SparsePauliOp`.

Args:
observable: The observable.

Returns:
The observable as :class:`~qiskit.quantum_info.SparsePauliOp`.

Raises:
AerError: when observable type cannot be converted to SparsePauliOp.
"""

if isinstance(observable, SparsePauliOp):
return observable
elif isinstance(observable, BaseOperator) and not isinstance(observable, BasePauli):
raise AerError(f"observable type not supported: {type(observable)}")
else:
if isinstance(observable, PauliList):
raise AerError(f"observable type not supported: {type(observable)}")
return SparsePauliOp(observable)


def _observable_key(observable: SparsePauliOp) -> tuple:
"""Private key function for SparsePauliOp.
Args:
observable: Input operator.

Returns:
Key for observables.
"""
return (
observable.paulis.z.tobytes(),
observable.paulis.x.tobytes(),
observable.paulis.phase.tobytes(),
observable.coeffs.tobytes(),
)


class Estimator(BaseEstimatorV1):
"""
Aer implmentation of Estimator.

Expand Down Expand Up @@ -592,7 +632,12 @@ def run(self, results: list[ExperimentResult]) -> tuple[float, dict]:
result = results[c_i]
count = result.data.counts
shots = sum(count.values())
basis = result.header.metadata["basis"]
# added for compatibility with qiskit 1.4 (metadata as attribute)
# and qiskit 2.0 (header as dict)
try:
basis = result.header.metadata["basis"]
except AttributeError:
basis = result.header["metadata"]["basis"]
indices = np.where(basis.z | basis.x)[0]
measured_paulis = PauliList.from_symplectic(
paulis.z[:, indices], paulis.x[:, indices], 0
Expand Down
121 changes: 117 additions & 4 deletions qiskit_aer/primitives/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,134 @@

from __future__ import annotations

from collections.abc import Sequence
from collections.abc import Sequence, Iterable

from warnings import warn
import numpy as np
from qiskit.circuit import ParameterExpression, QuantumCircuit
from qiskit.circuit import ParameterExpression, QuantumCircuit, Qubit
from qiskit.circuit.library.data_preparation import Initialize
from qiskit.compiler import transpile
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSamplerV1, SamplerResult
from qiskit.primitives.utils import final_measurement_mapping, init_circuit
from qiskit.quantum_info import Statevector
from qiskit.result import QuasiDistribution

from .. import AerSimulator


def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit:
"""Initialize state by converting the input to a quantum circuit.

Args:
state: The state as quantum circuit or statevector.

Returns:
The state as quantum circuit.
"""
if isinstance(state, QuantumCircuit):
return state
if not isinstance(state, Statevector):
state = Statevector(state)
qc = QuantumCircuit(state.num_qubits)
qc.append(Initialize(state), qargs=range(state.num_qubits))
return qc


def final_measurement_mapping(circuit: QuantumCircuit) -> dict[int, int]:
"""Return the final measurement mapping for the circuit.

Dict keys label measured qubits, whereas the values indicate the
classical bit onto which that qubits measurement result is stored.

Parameters:
circuit: Input quantum circuit.

Returns:
Mapping of qubits to classical bits for final measurements.
"""
active_qubits = list(range(circuit.num_qubits))
active_cbits = list(range(circuit.num_clbits))

# Find final measurements starting in back
mapping = {}
for item in circuit._data[::-1]:
if item.operation.name == "measure":
cbit = circuit.find_bit(item.clbits[0]).index
qbit = circuit.find_bit(item.qubits[0]).index
if cbit in active_cbits and qbit in active_qubits:
mapping[qbit] = cbit
active_cbits.remove(cbit)
active_qubits.remove(qbit)
elif item.operation.name not in ["barrier", "delay"]:
for qq in item.qubits:
_temp_qubit = circuit.find_bit(qq).index
if _temp_qubit in active_qubits:
active_qubits.remove(_temp_qubit)

if not active_cbits or not active_qubits:
break

# Sort so that classical bits are in numeric order low->high.
mapping = dict(sorted(mapping.items(), key=lambda item: item[1]))
return mapping


def _bits_key(bits: tuple[Qubit, ...], circuit: QuantumCircuit) -> tuple:
return tuple(
(
circuit.find_bit(bit).index,
tuple((reg[0].size, reg[0].name, reg[1]) for reg in circuit.find_bit(bit).registers),
)
for bit in bits
)


def _format_params(param):
if isinstance(param, np.ndarray):
return param.data.tobytes()
elif isinstance(param, QuantumCircuit):
return _circuit_key(param)
elif isinstance(param, Iterable):
return tuple(param)
return param


def _circuit_key(circuit: QuantumCircuit, functional: bool = True) -> tuple:
"""Private key function for QuantumCircuit.

This is the workaround until :meth:`QuantumCircuit.__hash__` will be introduced.
If key collision is found, please add elements to avoid it.

Args:
circuit: Input quantum circuit.
functional: If True, the returned key only includes functional data (i.e. execution related).

Returns:
Composite key for circuit.
"""
functional_key: tuple = (
circuit.num_qubits,
circuit.num_clbits,
circuit.num_parameters,
tuple( # circuit.data
(
_bits_key(data.qubits, circuit), # qubits
_bits_key(data.clbits, circuit), # clbits
data.operation.name, # operation.name
tuple(_format_params(param) for param in data.operation.params), # operation.params
)
for data in circuit.data
),
None if circuit._op_start_times is None else tuple(circuit._op_start_times),
)
if functional:
return functional_key
return (
circuit.name,
*functional_key,
)


class Sampler(BaseSamplerV1):
"""
Aer implementation of Sampler class.
Expand Down Expand Up @@ -155,7 +269,6 @@ def _run(
from typing import List

from qiskit.primitives.primitive_job import PrimitiveJob
from qiskit.primitives.utils import _circuit_key

circuit_indices: List[int] = []
for circuit in circuits:
Expand Down
18 changes: 5 additions & 13 deletions test/benchmark/simulator_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import sys
import numpy as np
from time import time
from qiskit.compiler import transpile, assemble
from qiskit.compiler import transpile
from qiskit_aer import AerSimulator, UnitarySimulator
from qiskit_aer.noise import NoiseModel, amplitude_damping_error, depolarizing_error

Expand Down Expand Up @@ -186,31 +186,23 @@ def add_expval(base, num_terms):
if runtime in self.TRANSPLIERS:
runtime_circuit = eval(self.TRANSPLIERS[runtime])(circuit)
if (runtime, app, measure, measure_count, qubit) not in QOBJS:
QOBJS[(runtime, app, measure, measure_count, qubit)] = assemble(
runtime_circuit, simulator, shots=measure_count
)
QOBJS[(runtime, app, measure, measure_count, qubit)] = runtime_circuit
return QOBJS[(runtime, app, measure, measure_count, qubit)]
else:
runtime_circuit = circuit
if (simulator, app, measure, measure_count, qubit) not in QOBJS:
QOBJS[(simulator, app, measure, measure_count, qubit)] = assemble(
runtime_circuit, simulator, shots=measure_count
)
QOBJS[(simulator, app, measure, measure_count, qubit)] = runtime_circuit
return QOBJS[(simulator, app, measure, measure_count, qubit)]
elif measure == self.MEASUREMENT_EXPVAL:
if runtime in self.TRANSPLIERS:
runtime_circuit = eval(self.TRANSPLIERS[runtime])(circuit)
if (runtime, app, measure, measure_count, qubit) not in QOBJS:
QOBJS[(runtime, app, measure, measure_count, qubit)] = assemble(
runtime_circuit, simulator, shots=1
)
QOBJS[(runtime, app, measure, measure_count, qubit)] = runtime_circuit
return QOBJS[(runtime, app, measure, measure_count, qubit)]
else:
runtime_circuit = circuit
if (simulator, app, measure, measure_count, qubit) not in QOBJS:
QOBJS[(simulator, app, measure, measure_count, qubit)] = assemble(
runtime_circuit, simulator, shots=1
)
QOBJS[(simulator, app, measure, measure_count, qubit)] = runtime_circuit
return QOBJS[(simulator, app, measure, measure_count, qubit)]

def _transpile(self, circuit, basis_gates):
Expand Down
Loading
Loading