Skip to content

Commit

Permalink
Fix C3SXGate to_matrix method (#12742)
Browse files Browse the repository at this point in the history
* Fix c3sx matrix

* Apply Jake's suggestion

(cherry picked from commit d86f995)

# Conflicts:
#	test/python/circuit/test_rust_equivalence.py
  • Loading branch information
ElePT authored and mergify[bot] committed Jul 9, 2024
1 parent 39e2dac commit 82d31e9
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 0 deletions.
2 changes: 2 additions & 0 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from qiskit.circuit._utils import _ctrl_state_to_int, with_gate_array, with_controlled_gate_array

_X_ARRAY = [[0, 1], [1, 0]]
_SX_ARRAY = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]]


@with_gate_array(_X_ARRAY)
Expand Down Expand Up @@ -488,6 +489,7 @@ def _define(self):
self.definition = qc


@with_controlled_gate_array(_SX_ARRAY, num_ctrl_qubits=3, cached_states=(7,))
class C3SXGate(SingletonControlledGate):
"""The 3-qubit controlled sqrt-X gate.
Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/fix-c3sx-gate-matrix-050cf9f9ac3b2b82.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
Fixed a missing decorator in :class:`.C3SXGate` that made it fail if :meth:`.Gate.to_matrix` was called.
The gate matrix is now return as expected.
6 changes: 6 additions & 0 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ def test_special_cases_equivalent_to_controlled_base_gate(self):
# Ensure that both the array form (if the gate overrides `__array__`) and the
# circuit-definition form are tested.
self.assertTrue(Operator(special_case_gate).equiv(naive_operator))
if not isinstance(special_case_gate, (MCXGate, MCPhaseGate, MCU1Gate)):
# Ensure that the to_matrix method yields the same result
np.testing.assert_allclose(
special_case_gate.to_matrix(), naive_operator.to_matrix(), atol=1e-8
)

if not isinstance(special_case_gate, CXGate):
# CX is treated like a primitive within Terra, and doesn't have a definition.
self.assertTrue(Operator(special_case_gate.definition).equiv(naive_operator))
Expand Down
236 changes: 236 additions & 0 deletions test/python/circuit/test_rust_equivalence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Rust gate definition tests"""

from functools import partial
from math import pi

from test import QiskitTestCase

import numpy as np

from qiskit.circuit import QuantumCircuit, CircuitInstruction
from qiskit.circuit.library.standard_gates import C3XGate, CU1Gate, CZGate, CCZGate
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
from qiskit.quantum_info import Operator

SKIP_LIST = {"rx", "ry", "ecr"}
CUSTOM_NAME_MAPPING = {"mcx": C3XGate()}


class TestRustGateEquivalence(QiskitTestCase):
"""Tests that compile time rust gate definitions is correct."""

def setUp(self):
super().setUp()
self.standard_gates = get_standard_gate_name_mapping()
self.standard_gates.update(CUSTOM_NAME_MAPPING)
# Pre-warm gate mapping cache, this is needed so rust -> py conversion is done
qc = QuantumCircuit(5)
for gate in self.standard_gates.values():
if getattr(gate, "_standard_gate", None):
if gate.params:
gate = gate.base_class(*[pi] * len(gate.params))
qc.append(gate, list(range(gate.num_qubits)))

def test_gate_cross_domain_conversion(self):
"""Test the rust -> python conversion returns the right class."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if standard_gate is None:
# Gate not in rust yet or no constructor method
continue
with self.subTest(name=name):
qc = QuantumCircuit(standard_gate.num_qubits)
qc._append(
CircuitInstruction(standard_gate, qubits=qc.qubits, params=gate_class.params)
)
self.assertEqual(qc.data[0].operation.base_class, gate_class.base_class)
self.assertEqual(qc.data[0].operation, gate_class)

def test_definitions(self):
"""Test definitions are the same in rust space."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if name in SKIP_LIST:
# gate does not have a rust definition yet
continue
if standard_gate is None:
# gate is not in rust yet
continue

with self.subTest(name=name):
params = [pi] * standard_gate._num_params()
py_def = gate_class.base_class(*params).definition
rs_def = standard_gate._get_definition(params)
if py_def is None:
self.assertIsNone(rs_def)
else:
rs_def = QuantumCircuit._from_circuit_data(rs_def)
for rs_inst, py_inst in zip(rs_def._data, py_def._data):
# In the following cases, Rust uses U but python still uses U3 and U2
if (
name in {"x", "y", "h", "r", "p", "u2", "u3", "cu", "crx"}
and rs_inst.operation.name == "u"
):
if py_inst.operation.name == "u3":
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
elif py_inst.operation.name == "u2":
self.assertEqual(
rs_inst.operation.params,
[
pi / 2,
py_inst.operation.params[0],
py_inst.operation.params[1],
],
)

self.assertEqual(
[py_def.find_bit(x).index for x in py_inst.qubits],
[rs_def.find_bit(x).index for x in rs_inst.qubits],
)
# In the following cases, Rust uses P but python still uses U1 and U3
elif (
name in {"z", "s", "sdg", "t", "tdg", "rz", "u1", "crx"}
and rs_inst.operation.name == "p"
):
if py_inst.operation.name == "u1":
self.assertEqual(py_inst.operation.name, "u1")
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
self.assertEqual(
[py_def.find_bit(x).index for x in py_inst.qubits],
[rs_def.find_bit(x).index for x in rs_inst.qubits],
)
else:
self.assertEqual(py_inst.operation.name, "u3")
self.assertEqual(
rs_inst.operation.params[0], py_inst.operation.params[2]
)
self.assertEqual(
[py_def.find_bit(x).index for x in py_inst.qubits],
[rs_def.find_bit(x).index for x in rs_inst.qubits],
)
# In the following cases, Rust uses CP but python still uses CU1
elif name in {"csx"} and rs_inst.operation.name == "cp":
self.assertEqual(py_inst.operation.name, "cu1")
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
self.assertEqual(
[py_def.find_bit(x).index for x in py_inst.qubits],
[rs_def.find_bit(x).index for x in rs_inst.qubits],
)
else:
self.assertEqual(py_inst.operation.name, rs_inst.operation.name)
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
self.assertEqual(
[py_def.find_bit(x).index for x in py_inst.qubits],
[rs_def.find_bit(x).index for x in rs_inst.qubits],
)

def test_matrix(self):
"""Test matrices are the same in rust space."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if standard_gate is None:
# gate is not in rust yet
continue

with self.subTest(name=name):
params = [0.1] * standard_gate._num_params()
py_def = gate_class.base_class(*params).to_matrix()
rs_def = standard_gate._to_matrix(params)
np.testing.assert_allclose(rs_def, py_def)

def test_name(self):
"""Test that the gate name properties match in rust space."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if standard_gate is None:
# gate is not in rust yet
continue
if gate_class.name == "mcx":
# ambiguous gate name
continue

with self.subTest(name=name):
self.assertEqual(gate_class.name, standard_gate.name)

def test_num_qubits(self):
"""Test the number of qubits are the same in rust space."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if standard_gate is None:
# gate is not in rust yet
continue

with self.subTest(name=name):
self.assertEqual(gate_class.num_qubits, standard_gate.num_qubits)

def test_num_params(self):
"""Test the number of parameters are the same in rust space."""
for name, gate_class in self.standard_gates.items():
standard_gate = getattr(gate_class, "_standard_gate", None)
if standard_gate is None:
# gate is not in rust yet
continue

with self.subTest(name=name):
self.assertEqual(
len(gate_class.params), standard_gate.num_params, msg=f"{name} not equal"
)

def test_non_default_controls(self):
"""Test that controlled gates with a non-default ctrl_state
are not using the standard rust representation."""
# CZ and CU1 are diagonal matrices with one non-1 term
# in the diagonal (see op_terms)
gate_classes = [CZGate, partial(CU1Gate, 0.1)]
op_terms = [-1, 0.99500417 + 0.09983342j]

for gate_cls, term in zip(gate_classes, op_terms):
with self.subTest(name="2q gates"):
default_op = np.diag([1, 1, 1, term])
non_default_op = np.diag([1, 1, term, 1])
state_out_map = {
1: default_op,
"1": default_op,
None: default_op,
0: non_default_op,
"0": non_default_op,
}
for state, op in state_out_map.items():
circuit = QuantumCircuit(2)
gate = gate_cls(ctrl_state=state)
circuit.append(gate, [0, 1])
self.assertIsNotNone(getattr(gate, "_standard_gate", None))
np.testing.assert_almost_equal(circuit.data[0].operation.to_matrix(), op)

with self.subTest(name="3q gate"):
default_op = np.diag([1, 1, 1, 1, 1, 1, 1, -1])
non_default_op_0 = np.diag([1, 1, 1, 1, -1, 1, 1, 1])
non_default_op_1 = np.diag([1, 1, 1, 1, 1, -1, 1, 1])
non_default_op_2 = np.diag([1, 1, 1, 1, 1, 1, -1, 1])
state_out_map = {
3: default_op,
"11": default_op,
None: default_op,
0: non_default_op_0,
1: non_default_op_1,
"01": non_default_op_1,
"10": non_default_op_2,
}
for state, op in state_out_map.items():
circuit = QuantumCircuit(3)
gate = CCZGate(ctrl_state=state)
circuit.append(gate, [0, 1, 2])
self.assertIsNotNone(getattr(gate, "_standard_gate", None))
np.testing.assert_almost_equal(Operator(circuit.data[0].operation).to_matrix(), op)

0 comments on commit 82d31e9

Please sign in to comment.