Skip to content

Commit

Permalink
Add explicit tests of template matching pass
Browse files Browse the repository at this point in the history
This adds several tests of the exact form produced by running the
template-matching transpiler pass, including those with purely numeric
circuits and those with symbolic parameters.
  • Loading branch information
jakelishman committed Mar 24, 2022
1 parent 908f87d commit fc83156
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions test/python/transpiler/test_template_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@
from qiskit.transpiler.exceptions import TranspilerError


def _ry_to_rz_template_pass(parameter=None, extra_costs=None):
"""Create a simple pass manager that runs a template optimisation with a single transformation.
It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where
if ``parameter`` is given, it will be the instance used in the template."""
if parameter is None:
parameter = Parameter("_ry_rz_template_inner")
template = QuantumCircuit(1)
template.rx(-np.pi / 2, 0)
template.ry(parameter, 0)
template.rx(np.pi / 2, 0)
template.rz(-parameter, 0)

costs = {"rx": 16, "ry": 16, "rz": 0}
if extra_costs is not None:
costs.update(extra_costs)

return PassManager(TemplateOptimization([template], user_cost_dict=costs))


class TestTemplateMatching(QiskitTestCase):
"""Test the TemplateOptimization pass."""

Expand Down Expand Up @@ -458,6 +477,135 @@ def test_two_parameter_template(self):
)
)

def test_exact_substitution_numeric_parameter(self):
"""Test that a template match produces the expected value for numeric parameters."""
circuit_in = QuantumCircuit(1)
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(1.45, 0)
circuit_in.rx(np.pi / 2, 0)
circuit_out = _ry_to_rz_template_pass().run(circuit_in)

expected = QuantumCircuit(1)
expected.rz(1.45, 0)
self.assertEqual(circuit_out, expected)

def test_exact_substitution_symbolic_parameter(self):
"""Test that a template match produces the expected value for numeric parameters."""
a_circuit = Parameter("a")
circuit_in = QuantumCircuit(1)
circuit_in.h(0)
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(a_circuit, 0)
circuit_in.rx(np.pi / 2, 0)
circuit_out = _ry_to_rz_template_pass(extra_costs={"h": 1}).run(circuit_in)

expected = QuantumCircuit(1)
expected.h(0)
expected.rz(a_circuit, 0)
self.assertEqual(circuit_out, expected)

def test_naming_clash(self):
"""Test that the template matching works and correctly replaces a template if there is a
naming clash between it and the circuit. This should include binding a partial match with a
parameter."""
# Two instances of parameters with the same name---this is how naming clashes might occur.
a_template = Parameter("a")
a_circuit = Parameter("a")
circuit_in = QuantumCircuit(1)
circuit_in.h(0)
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(a_circuit, 0)
circuit_in.rx(np.pi / 2, 0)
circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in)

expected = QuantumCircuit(1)
expected.h(0)
expected.rz(a_circuit, 0)
self.assertEqual(circuit_out, expected)
# Ensure that the bound parameter in the output is referentially the same as the one we put
# in the input circuit..
self.assertEqual(len(circuit_out.parameters), 1)
self.assertIs(circuit_in.parameters[0], a_circuit)
self.assertIs(circuit_out.parameters[0], a_circuit)

def test_naming_clash_in_expression(self):
"""Test that the template matching works and correctly replaces a template if there is a
naming clash between it and the circuit. This should include binding a partial match with a
parameter."""
a_template = Parameter("a")
a_circuit = Parameter("a")
circuit_in = QuantumCircuit(1)
circuit_in.h(0)
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(2 * a_circuit, 0)
circuit_in.rx(np.pi / 2, 0)
circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in)

expected = QuantumCircuit(1)
expected.h(0)
expected.rz(2 * a_circuit, 0)
self.assertEqual(circuit_out, expected)
# Ensure that the bound parameter in the output is referentially the same as the one we put
# in the input circuit..
self.assertEqual(len(circuit_out.parameters), 1)
self.assertIs(circuit_in.parameters[0], a_circuit)
self.assertIs(circuit_out.parameters[0], a_circuit)

def test_template_match_with_uninvolved_parameter(self):
"""Test that the template matching algorithm succeeds at matching a circuit that contains an
unbound parameter that is not involved in the subcircuit that matches."""
b_circuit = Parameter("b")
circuit_in = QuantumCircuit(2)
circuit_in.rz(b_circuit, 0)
circuit_in.rx(-np.pi / 2, 1)
circuit_in.ry(1.45, 1)
circuit_in.rx(np.pi / 2, 1)
circuit_out = _ry_to_rz_template_pass().run(circuit_in)

expected = QuantumCircuit(2)
expected.rz(b_circuit, 0)
expected.rz(1.45, 1)
self.assertEqual(circuit_out, expected)

def test_multiple_numeric_matches_same_template(self):
"""Test that the template matching will change both instances of a partial match within a
longer circuit."""
circuit_in = QuantumCircuit(2)
# Qubit 0
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(1.32, 0)
circuit_in.rx(np.pi / 2, 0)
# Qubit 1
circuit_in.rx(-np.pi / 2, 1)
circuit_in.ry(2.54, 1)
circuit_in.rx(np.pi / 2, 1)
circuit_out = _ry_to_rz_template_pass().run(circuit_in)

expected = QuantumCircuit(2)
expected.rz(1.32, 0)
expected.rz(2.54, 1)
self.assertEqual(circuit_out, expected)

def test_multiple_symbolic_matches_same_template(self):
"""Test that the template matching will change both instances of a partial match within a
longer circuit."""
a, b = Parameter("a"), Parameter("b")
circuit_in = QuantumCircuit(2)
# Qubit 0
circuit_in.rx(-np.pi / 2, 0)
circuit_in.ry(a, 0)
circuit_in.rx(np.pi / 2, 0)
# Qubit 1
circuit_in.rx(-np.pi / 2, 1)
circuit_in.ry(b, 1)
circuit_in.rx(np.pi / 2, 1)
circuit_out = _ry_to_rz_template_pass().run(circuit_in)

expected = QuantumCircuit(2)
expected.rz(a, 0)
expected.rz(b, 1)
self.assertEqual(circuit_out, expected)


if __name__ == "__main__":
unittest.main()

0 comments on commit fc83156

Please sign in to comment.