Skip to content

Commit

Permalink
Implement __array__ for qasm2._DefinedGate (#12119) (#12523)
Browse files Browse the repository at this point in the history
* Implement `__array__` for `qasm2._DefinedGate`

Gates defined from OpenQASM 2 should have a well-defined matrix form (up
to global phase) whenever all the constituent parts of the definition
do. This manually makes such a matrix available.

* Fix signature for Numpy 2.0

(cherry picked from commit 767bd07)

Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
  • Loading branch information
mergify[bot] and jakelishman authored Jun 6, 2024
1 parent bac400d commit c148a5e
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 0 deletions.
8 changes: 8 additions & 0 deletions qiskit/qasm2/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import math
from typing import Iterable, Callable

import numpy as np

from qiskit.circuit import (
Barrier,
CircuitInstruction,
Expand All @@ -30,6 +32,7 @@
Reset,
library as lib,
)
from qiskit.quantum_info import Operator
from qiskit._accelerate.qasm2 import (
OpCode,
UnaryOpCode,
Expand Down Expand Up @@ -315,6 +318,11 @@ def _define(self):
raise ValueError(f"received invalid bytecode to build gate: {op}")
self._definition = qc

def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
return np.asarray(Operator(self.definition), dtype=dtype)

# It's fiddly to implement pickling for PyO3 types (the bytecode stream), so instead if we need
# to pickle ourselves, we just eagerly create the definition and pickle that.

Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/qasm2-to-matrix-c707fe1e61b3987f.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
fixes:
- |
Custom gates (those stemming from a ``gate`` statement) in imported OpenQASM 2 programs will now
have an :meth:`.Gate.to_matrix` implementation. Previously they would have no matrix definition,
meaning that roundtrips through OpenQASM 2 could needlessly lose the ability to derive the gate
matrix. Note, though, that the matrix is calculated by recursively finding the matrices of the
inner gate definitions, as :class:`.Operator` does, which might be less performant than before
the round-trip.
26 changes: 26 additions & 0 deletions test/python/qasm2/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import tempfile

import ddt
import numpy as np

import qiskit.qasm2
from qiskit import qpy
Expand All @@ -34,6 +35,7 @@
Qubit,
library as lib,
)
from qiskit.quantum_info import Operator
from test import QiskitTestCase # pylint: disable=wrong-import-order

from . import gate_builder
Expand Down Expand Up @@ -906,6 +908,30 @@ def test_conditioned_broadcast_against_empty_register(self):
)
self.assertEqual(parsed, qc)

def test_has_to_matrix(self):
program = """
OPENQASM 2.0;
include "qelib1.inc";
qreg qr[1];
gate my_gate(a) q {
rz(a) q;
rx(pi / 2) q;
rz(-a) q;
}
my_gate(1.0) qr[0];
"""
parsed = qiskit.qasm2.loads(program)
expected = (
lib.RZGate(-1.0).to_matrix()
@ lib.RXGate(math.pi / 2).to_matrix()
@ lib.RZGate(1.0).to_matrix()
)
defined_gate = parsed.data[0].operation
self.assertEqual(defined_gate.name, "my_gate")
np.testing.assert_allclose(defined_gate.to_matrix(), expected, atol=1e-14, rtol=0)
# Also test that the standard `Operator` method on the whole circuit still works.
np.testing.assert_allclose(Operator(parsed), expected, atol=1e-14, rtol=0)


class TestReset(QiskitTestCase):
def test_single(self):
Expand Down

0 comments on commit c148a5e

Please sign in to comment.