Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
abbycross committed Oct 23, 2024
2 parents b533bc5 + 9a1d8d3 commit e0760db
Show file tree
Hide file tree
Showing 13 changed files with 2,750 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ qiskit-circuit.workspace = true
thiserror.workspace = true
ndarray_einsum_beta = "0.7"
once_cell = "1.20.2"
bytemuck.workspace = true

[dependencies.smallvec]
workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod remove_diagonal_gates_before_measure;
pub mod results;
pub mod sabre;
pub mod sampled_exp_val;
pub mod sparse_observable;
pub mod sparse_pauli_op;
pub mod split_2q_unitaries;
pub mod star_prerouting;
Expand Down
1,709 changes: 1,709 additions & 0 deletions crates/accelerate/src/sparse_observable.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/circuit/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ pub static XX_DECOMPOSER: ImportOnceCell =
ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXDecomposer");
pub static XX_EMBODIMENTS: ImportOnceCell =
ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXEmbodiments");
pub static NUMPY_COPY_ONLY_IF_NEEDED: ImportOnceCell =
ImportOnceCell::new("qiskit._numpy_compat", "COPY_ONLY_IF_NEEDED");

/// A mapping from the enum variant in crate::operations::StandardGate to the python
/// module path and class name to import it. This is used to populate the conversion table
Expand Down
1 change: 1 addition & 0 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(m, ::qiskit_accelerate::results::results, "results")?;
add_submodule(m, ::qiskit_accelerate::sabre::sabre, "sabre")?;
add_submodule(m, ::qiskit_accelerate::sampled_exp_val::sampled_exp_val, "sampled_exp_val")?;
add_submodule(m, ::qiskit_accelerate::sparse_observable::sparse_observable, "sparse_observable")?;
add_submodule(m, ::qiskit_accelerate::sparse_pauli_op::sparse_pauli_op, "sparse_pauli_op")?;
add_submodule(m, ::qiskit_accelerate::split_2q_unitaries::split_2q_unitaries_mod, "split_2q_unitaries")?;
add_submodule(m, ::qiskit_accelerate::star_prerouting::star_prerouting, "star_prerouting")?;
Expand Down
23 changes: 18 additions & 5 deletions crates/qasm2/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,8 +827,16 @@ impl State {
}
bc.push(Some(InternalBytecode::EndDeclareGate {}));
self.gate_symbols.clear();
self.define_gate(Some(&gate_token), name, num_params, num_qubits)?;
Ok(statements + 2)
let num_bytecode = statements + 2;
if self.define_gate(Some(&gate_token), name, num_params, num_qubits)? {
Ok(num_bytecode)
} else {
// The gate was built-in, so we don't actually need to emit the bytecode. This is
// uncommon, so it doesn't matter too much that we throw away allocation work we did -
// it still helps that we verified that the gate body was valid OpenQASM 2.
bc.truncate(bc.len() - num_bytecode);
Ok(0)
}
}

/// Parse an `opaque` statement. This assumes that the `opaque` token is still in the token
Expand Down Expand Up @@ -1634,6 +1642,9 @@ impl State {
/// bytecode because not all gate definitions need something passing to Python. For example,
/// the Python parser initializes its state including the built-in gates `U` and `CX`, and
/// handles the `qelib1.inc` include specially as well.
///
/// Returns whether the gate needs to be defined in Python space (`true`) or if it was some sort
/// of built-in that doesn't need the definition (`false`).
fn define_gate(
&mut self,
owner: Option<&Token>,
Expand Down Expand Up @@ -1685,12 +1696,14 @@ impl State {
}
match self.symbols.get(&name) {
None => {
// The gate wasn't a built-in, so we need to move the symbol in, but we don't
// need to increment the number of gates because it's already got a gate ID
// assigned.
self.symbols.insert(name, symbol.into());
self.num_gates += 1;
Ok(true)
Ok(false)
}
Some(GlobalSymbol::Gate { .. }) => {
self.symbols.insert(name, symbol.into());
// The gate was built-in and we can ignore the new definition (it's the same).
Ok(false)
}
_ => already_defined(self, name),
Expand Down
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
sys.modules["qiskit._accelerate.results"] = _accelerate.results
sys.modules["qiskit._accelerate.sabre"] = _accelerate.sabre
sys.modules["qiskit._accelerate.sampled_exp_val"] = _accelerate.sampled_exp_val
sys.modules["qiskit._accelerate.sparse_observable"] = _accelerate.sparse_observable
sys.modules["qiskit._accelerate.sparse_pauli_op"] = _accelerate.sparse_pauli_op
sys.modules["qiskit._accelerate.star_prerouting"] = _accelerate.star_prerouting
sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap
Expand Down
4 changes: 4 additions & 0 deletions qiskit/quantum_info/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
Pauli
Clifford
ScalarOp
SparseObservable
SparsePauliOp
CNOTDihedral
PauliList
Expand Down Expand Up @@ -113,6 +114,9 @@
"""

from __future__ import annotations

from qiskit._accelerate.sparse_observable import SparseObservable

from .analysis import hellinger_distance, hellinger_fidelity, Z2Symmetries
from .operators import (
Clifford,
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/qasm2-builtin-gate-d80c2868cdf5f958.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
The OpenQASM 2 importer previously would output incorrect :class:`.Gate` instances for gate
calls referring to a ``gate`` definition that followed a prior ``gate`` definition that was
being treated as a built-in operation by a :class:`~.qasm2.CustomInstruction`. See
`#13339 <https://github.com/Qiskit/qiskit/issues/13339>`__ for more detail.
32 changes: 32 additions & 0 deletions releasenotes/notes/sparse-observable-7de70dcdf6962a64.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
features_quantum_info:
- |
A new observable class has been added. :class:`.SparseObservable` represents observables as a
sum of terms, similar to :class:`.SparsePauliOp`, but with two core differences:
1. Each complete term is stored as (effectively) a series of ``(qubit, bit_term)`` pairs,
without storing qubits that undergo the identity for that term. This significantly improves
the memory usage of observables such as the weighted sum of Paulis :math:`\sum_i c_i Z_i`.
2. The single-qubit term alphabet is overcomplete for the operator space; it can represent Pauli
operators (like :class:`.SparsePauliOp`), but also projectors onto the eigenstates of the
Pauli operators, like :math:`\lvert 0\rangle\langle 0\rangle`. Such projectors can be
measured on hardware equally as efficiently as their corresponding Pauli operator, but
:class:`.SparsePauliOp` would require an exponential number of terms to represent
:math:`{\lvert0\rangle\langle0\rvert}^{\otimes n}` over :math:`n` qubits, while
:class:`.SparseObservable` needs only a single term.
You can construct and manipulate :class:`.SparseObservable` using an interface familiar to users
of :class:`.SparsePauliOp`::
from qiskit.quantum_info import SparseObservable
obs = SparseObservable.from_sparse_list([
("XZY", (2, 1, 0), 1.5j),
("+-", (100, 99), 0.5j),
("01", (50, 49), 0.5),
])
:class:`.SparseObservable` is not currently supported as an input format to the primitives
(:mod:`qiskit.primitives`), but we expect to expand these interfaces to include them in the
future.
41 changes: 41 additions & 0 deletions test/python/qasm2/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,47 @@ def __init__(self, a):
qc.append(MyGate(0.5), [0])
self.assertEqual(parsed, qc)

def test_compatible_definition_of_builtin_is_ignored(self):
program = """
qreg q[1];
gate my_gate a { U(0, 0, 0) a; }
my_gate q[0];
"""

class MyGate(Gate):
def __init__(self):
super().__init__("my_gate", 1, [])

def _define(self):
self._definition = QuantumCircuit(1)
self._definition.z(0)

parsed = qiskit.qasm2.loads(
program, custom_instructions=[qiskit.qasm2.CustomInstruction("my_gate", 0, 1, MyGate)]
)
self.assertEqual(parsed.data[0].operation.definition, MyGate().definition)

def test_gates_defined_after_a_builtin_align(self):
"""It's easy to get out of sync between the Rust-space and Python-space components when
``builtin=True``. See https://github.com/Qiskit/qiskit/issues/13339."""
program = """
OPENQASM 2.0;
gate first a { U(0, 0, 0) a; }
gate second a { U(pi, pi, pi) a; }
qreg q[1];
first q[0];
second q[0];
"""
custom = qiskit.qasm2.CustomInstruction("first", 0, 1, lib.XGate, builtin=True)
parsed = qiskit.qasm2.loads(program, custom_instructions=[custom])
# Provided definitions for built-in gates are ignored, so it should be an XGate directly.
self.assertEqual(parsed.data[0].operation, lib.XGate())
self.assertEqual(parsed.data[1].operation.name, "second")
defn = parsed.data[1].operation.definition.copy_empty_like()
defn.u(math.pi, math.pi, math.pi, 0)
self.assertEqual(parsed.data[1].operation.definition, defn)


class TestCustomClassical(QiskitTestCase):
def test_qiskit_extensions(self):
Expand Down
Loading

0 comments on commit e0760db

Please sign in to comment.