Skip to content

Commit

Permalink
Merge branch 'main' into add-rust-bind-params
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Aug 8, 2024
2 parents 6eead5e + e7ee189 commit e7f2550
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 118 deletions.
4 changes: 2 additions & 2 deletions crates/accelerate/src/target_transpiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ impl Target {
match instruction {
TargetOperation::Variadic(_) => {
qargs_val = PropsMap::with_capacity(1);
qargs_val.extend([(None, None)].into_iter());
qargs_val.extend([(None, None)]);
self.variable_class_operations.insert(name.to_string());
}
TargetOperation::Normal(_) => {
Expand Down Expand Up @@ -872,7 +872,7 @@ impl Target {
.unwrap()
.extract::<GateMapState>()?
.into_iter()
.map(|(name, prop_map)| (name, PropsMap::from_iter(prop_map.into_iter()))),
.map(|(name, prop_map)| (name, PropsMap::from_iter(prop_map))),
);
self._gate_name_map = state
.get_item("gate_name_map")?
Expand Down
10 changes: 3 additions & 7 deletions crates/accelerate/src/target_transpiler/nullable_index_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ where
pub fn iter(&self) -> Iter<K, V> {
Iter {
map: self.map.iter(),
null_value: &self.null_val,
null_value: self.null_val.as_ref(),
}
}

Expand Down Expand Up @@ -209,7 +209,7 @@ where
/// Iterator for the key-value pairs in `NullableIndexMap`.
pub struct Iter<'a, K, V> {
map: BaseIter<'a, K, V>,
null_value: &'a Option<V>,
null_value: Option<&'a V>,
}

impl<'a, K, V> Iterator for Iter<'a, K, V> {
Expand All @@ -218,12 +218,8 @@ impl<'a, K, V> Iterator for Iter<'a, K, V> {
fn next(&mut self) -> Option<Self::Item> {
if let Some((key, val)) = self.map.next() {
Some((Some(key), val))
} else if let Some(value) = self.null_value {
let value = value;
self.null_value = &None;
Some((None, value))
} else {
None
self.null_value.take().map(|value| (None, value))
}
}

Expand Down
17 changes: 12 additions & 5 deletions qiskit/primitives/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from qiskit.circuit import QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.quantum_info import Statevector
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.utils.deprecation import deprecate_func

Expand All @@ -31,7 +30,7 @@
from .utils import (
_circuit_key,
_observable_key,
bound_circuit_to_instruction,
_statevector_from_circuit,
init_observable,
)

Expand All @@ -43,13 +42,21 @@ class Estimator(BaseEstimator[PrimitiveJob[EstimatorResult]]):
:Run Options:
- **shots** (None or int) --
The number of shots. If None, it calculates the exact expectation
values. Otherwise, it samples from normal distributions with standard errors as standard
The number of shots. If None, it calculates the expectation values
with full state vector simulation.
Otherwise, it samples from normal distributions with standard errors as standard
deviations using normal distribution approximation.
- **seed** (np.random.Generator or int) --
Set a fixed seed or generator for the normal distribution. If shots is None,
this option is ignored.
.. note::
The result of this class is exact if the circuit contains only unitary operations.
On the other hand, the result could be stochastic if the circuit contains a non-unitary
operation such as a reset for a some subsystems.
The stochastic result can be made reproducible by setting ``seed``, e.g.,
``Estimator(options={"seed":123})``.
"""

@deprecate_func(
Expand Down Expand Up @@ -112,7 +119,7 @@ def _call(
f"The number of qubits of a circuit ({circ.num_qubits}) does not match "
f"the number of qubits of a observable ({obs.num_qubits})."
)
final_state = Statevector(bound_circuit_to_instruction(circ))
final_state = _statevector_from_circuit(circ, rng)
expectation_value = final_state.expectation_value(obs)
if shots is None:
expectation_values.append(expectation_value)
Expand Down
13 changes: 10 additions & 3 deletions qiskit/primitives/statevector_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@

import numpy as np

from qiskit.quantum_info import SparsePauliOp, Statevector
from qiskit.quantum_info import SparsePauliOp

from .base import BaseEstimatorV2
from .containers import DataBin, EstimatorPubLike, PrimitiveResult, PubResult
from .containers.estimator_pub import EstimatorPub
from .primitive_job import PrimitiveJob
from .utils import bound_circuit_to_instruction
from .utils import _statevector_from_circuit


class StatevectorEstimator(BaseEstimatorV2):
Expand All @@ -41,6 +41,13 @@ class StatevectorEstimator(BaseEstimatorV2):
called an estimator primitive unified bloc (PUB), produces its own array-based result. The
:meth:`~.EstimatorV2.run` method can be given a sequence of pubs to run in one call.
.. note::
The result of this class is exact if the circuit contains only unitary operations.
On the other hand, the result could be stochastic if the circuit contains a non-unitary
operation such as a reset for a some subsystems.
The stochastic result can be made reproducible by setting ``seed``, e.g.,
``StatevectorEstimator(seed=123)``.
.. plot::
:include-source:
Expand Down Expand Up @@ -151,7 +158,7 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult:
for index in np.ndindex(*bc_circuits.shape):
bound_circuit = bc_circuits[index]
observable = bc_obs[index]
final_state = Statevector(bound_circuit_to_instruction(bound_circuit))
final_state = _statevector_from_circuit(bound_circuit, rng)
paulis, coeffs = zip(*observable.items())
obs = SparsePauliOp(paulis, coeffs) # TODO: support non Pauli operators
expectation_value = np.real_if_close(final_state.expectation_value(obs))
Expand Down
20 changes: 20 additions & 0 deletions qiskit/primitives/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,23 @@ def bound_circuit_to_instruction(circuit: QuantumCircuit) -> Instruction:
)
inst.definition = circuit
return inst


def _statevector_from_circuit(
circuit: QuantumCircuit, rng: np.random.Generator | None
) -> Statevector:
"""Generate a statevector from a circuit
If the input circuit includes any resets for a some subsystem,
:meth:`.Statevector.reset` behaves in a stochastic way in :meth:`.Statevector.evolve`.
This function sets a random number generator to be reproducible.
See :meth:`.Statevector.reset` for details.
Args:
circuit: The quantum circuit.
seed: The random number generator or None.
"""
sv = Statevector.from_int(0, 2**circuit.num_qubits)
sv.seed(rng)
return sv.evolve(bound_circuit_to_instruction(circuit))
3 changes: 2 additions & 1 deletion qiskit/transpiler/passes/basis/basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ def run(self, dag):
# If the source basis is a subset of the target basis and we have no circuit
# instructions on qargs that have non-global operations there is nothing to
# translate and we can exit early.
if source_basis.issubset(target_basis) and not qargs_local_source_basis:
source_basis_names = {x[0] for x in source_basis}
if source_basis_names.issubset(target_basis) and not qargs_local_source_basis:
return dag

logger.info(
Expand Down
8 changes: 7 additions & 1 deletion qiskit/transpiler/passes/optimization/consolidate_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from qiskit.transpiler.passes.synthesis import unitary_synthesis
from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
from qiskit._accelerate.convert_2q_block_matrix import blocks_to_matrix
from qiskit.exceptions import QiskitError

from .collect_1q_runs import Collect1qRuns
from .collect_2q_blocks import Collect2qBlocks
Expand Down Expand Up @@ -125,7 +126,12 @@ def run(self, dag):
qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs])
unitary = UnitaryGate(Operator(qc), check_input=False)
else:
matrix = blocks_to_matrix(block, block_index_map)
try:
matrix = blocks_to_matrix(block, block_index_map)
except QiskitError:
# If building a matrix for the block fails we should not consolidate it
# because there is nothing we can do with it.
continue
unitary = UnitaryGate(matrix, check_input=False)

max_2q_depth = 20 # If depth > 20, there will be 1q gates to consolidate.
Expand Down
64 changes: 10 additions & 54 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import os

from qiskit.circuit import Instruction
from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries
from qiskit.transpiler.passmanager import PassManager
from qiskit.transpiler.exceptions import TranspilerError
Expand Down Expand Up @@ -66,7 +65,6 @@
CYGate,
SXGate,
SXdgGate,
get_standard_gate_name_mapping,
)
from qiskit.utils.parallel import CPU_COUNT
from qiskit import user_config
Expand Down Expand Up @@ -173,58 +171,16 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
)
)
init.append(CommutativeCancellation())
# skip peephole optimization before routing if target basis gate set is discrete,
# i.e. only consists of Cliffords that an user might want to keep
# use rz, sx, x, cx as basis, rely on physical optimziation to fix everything later one
stdgates = get_standard_gate_name_mapping()

def _is_one_op_non_discrete(ops):
"""Checks if one operation in `ops` is not discrete, i.e. is parameterizable
Args:
ops (List(Operation)): list of operations to check
Returns
True if at least one operation in `ops` is not discrete, False otherwise
"""
found_one_continuous_gate = False
for op in ops:
if isinstance(op, str):
if op in _discrete_skipped_ops:
continue
op = stdgates.get(op, None)

if op is not None and op.name in _discrete_skipped_ops:
continue

if op is None or not isinstance(op, Instruction):
return False

if len(op.params) > 0:
found_one_continuous_gate = True
return found_one_continuous_gate

target = pass_manager_config.target
basis = pass_manager_config.basis_gates
# consolidate gates before routing if the user did not specify a discrete basis gate, i.e.
# * no target or basis gate set has been specified
# * target has been specified, and we have one non-discrete gate in the target's spec
# * basis gates have been specified, and we have one non-discrete gate in that set
do_consolidate_blocks_init = target is None and basis is None
do_consolidate_blocks_init |= target is not None and _is_one_op_non_discrete(
target.operations
)
do_consolidate_blocks_init |= basis is not None and _is_one_op_non_discrete(basis)

if do_consolidate_blocks_init:
init.append(Collect2qBlocks())
init.append(ConsolidateBlocks())
# If approximation degree is None that indicates a request to approximate up to the
# error rates in the target. However, in the init stage we don't yet know the target
# qubits being used to figure out the fidelity so just use the default fidelity parameter
# in this case.
if pass_manager_config.approximation_degree is not None:
init.append(Split2QUnitaries(pass_manager_config.approximation_degree))
else:
init.append(Split2QUnitaries())
init.append(Collect2qBlocks())
init.append(ConsolidateBlocks())
# If approximation degree is None that indicates a request to approximate up to the
# error rates in the target. However, in the init stage we don't yet know the target
# qubits being used to figure out the fidelity so just use the default fidelity parameter
# in this case.
if pass_manager_config.approximation_degree is not None:
init.append(Split2QUnitaries(pass_manager_config.approximation_degree))
else:
init.append(Split2QUnitaries())
else:
raise TranspilerError(f"Invalid optimization level {optimization_level}")
return init
Expand Down
13 changes: 10 additions & 3 deletions qiskit/visualization/dag_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,13 @@ def node_attr_func(node):
label = register_bit_labels.get(
node.wire, f"q_{dag.find_bit(node.wire).index}"
)
else:
elif isinstance(node.wire, Clbit):
label = register_bit_labels.get(
node.wire, f"c_{dag.find_bit(node.wire).index}"
)
else:
label = str(node.wire.name)

n["label"] = label
n["color"] = "black"
n["style"] = "filled"
Expand All @@ -187,10 +190,12 @@ def node_attr_func(node):
label = register_bit_labels.get(
node.wire, f"q[{dag.find_bit(node.wire).index}]"
)
else:
elif isinstance(node.wire, Clbit):
label = register_bit_labels.get(
node.wire, f"c[{dag.find_bit(node.wire).index}]"
)
else:
label = str(node.wire.name)
n["label"] = label
n["color"] = "black"
n["style"] = "filled"
Expand All @@ -203,8 +208,10 @@ def edge_attr_func(edge):
e = {}
if isinstance(edge, Qubit):
label = register_bit_labels.get(edge, f"q_{dag.find_bit(edge).index}")
else:
elif isinstance(edge, Clbit):
label = register_bit_labels.get(edge, f"c_{dag.find_bit(edge).index}")
else:
label = str(edge.name)
e["label"] = label
return e

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
fixes:
- |
Fixed a bug in the :class:`.ConsolidateBlocks` transpiler pass, when the
input circuit contains a custom opaque gate and neither the
``basis_gates`` or ``target`` options are set the pass would raise a
``QiskitError`` and fail. This has been corrected so that the in these
situations the transpiler pass will not consolidate the block identified
containing a custom gate instead of failing.
19 changes: 19 additions & 0 deletions releasenotes/notes/fix-estimator-reset-9e7539776df4cac4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
features_primitives:
- |
:class:`.Estimator` and :class:`.StatevectorEstimator` return
expectation values in a stochastic way if the input circuit includes
a reset for a some subsystems.
The result was not reproducible, but it is now reproducible
if a random seed is set. For example::
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator(seed=123)
or::
from qiskit.primitives import Estimator
estimator = Estimator(options={"seed":123})
8 changes: 8 additions & 0 deletions releasenotes/notes/fix-var-wires-4ebc40e0b19df253.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Fixed an issue with :func:`.dag_drawer` and :meth:`.DAGCircuit.draw`
when attempting to visualize a :class:`.DAGCircuit` instance that contained
:class:`.Var` wires. The visualizer would raise an exception trying to
do this which has been fixed so the expected visualization will be
generated.
Loading

0 comments on commit e7f2550

Please sign in to comment.