qiskit/test/python/transpiler/test_template_matching.py

799 lines
33 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# 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.
"""Test the TemplateOptimization pass."""
import unittest
import numpy as np
from qiskit.circuit.commutation_library import SessionCommutationChecker as scc
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import Operator
from qiskit.circuit.library.templates.nct import template_nct_2a_2, template_nct_5a_3
from qiskit.circuit.library.templates.clifford import (
clifford_2_1,
clifford_2_2,
clifford_2_3,
clifford_2_4,
clifford_3_1,
clifford_4_1,
clifford_4_2,
)
from qiskit.converters.circuit_to_dag import circuit_to_dag
from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import TemplateOptimization
from qiskit.transpiler.passes.calibration.rzx_templates import rzx_templates
from qiskit.transpiler.exceptions import TranspilerError
from test.python.quantum_info.operators.symplectic.test_clifford import ( # pylint: disable=wrong-import-order
random_clifford_circuit,
)
from test import QiskitTestCase # pylint: disable=wrong-import-order
def _ry_to_rz_template_pass(parameter: Parameter = None, extra_costs=None):
"""Create a simple pass manager that runs a template optimization 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) # pylint: disable=invalid-unary-operand-type
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."""
def test_pass_cx_cancellation_no_template_given(self):
"""
Check the cancellation of CX gates for the apply of the three basic
template x-x, cx-cx. ccx-ccx.
"""
qr = QuantumRegister(3)
circuit_in = QuantumCircuit(qr)
circuit_in.h(qr[0])
circuit_in.h(qr[0])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[1], qr[0])
circuit_in.cx(qr[1], qr[0])
pass_manager = PassManager()
pass_manager.append(TemplateOptimization())
circuit_in_opt = pass_manager.run(circuit_in)
circuit_out = QuantumCircuit(qr)
circuit_out.h(qr[0])
circuit_out.h(qr[0])
self.assertEqual(circuit_in_opt, circuit_out)
def test_pass_cx_cancellation_own_template(self):
"""
Check the cancellation of CX gates for the apply of a self made template cx-cx.
"""
qr = QuantumRegister(2, "qr")
circuit_in = QuantumCircuit(qr)
circuit_in.h(qr[0])
circuit_in.h(qr[0])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[1], qr[0])
circuit_in.cx(qr[1], qr[0])
dag_in = circuit_to_dag(circuit_in)
qrt = QuantumRegister(2, "qrc")
qct = QuantumCircuit(qrt)
qct.cx(0, 1)
qct.cx(0, 1)
template_list = [qct]
pass_ = TemplateOptimization(template_list)
dag_opt = pass_.run(dag_in)
circuit_expected = QuantumCircuit(qr)
circuit_expected.h(qr[0])
circuit_expected.h(qr[0])
dag_expected = circuit_to_dag(circuit_expected)
self.assertEqual(dag_opt, dag_expected)
def test_pass_cx_cancellation_template_from_library(self):
"""
Check the cancellation of CX gates for the apply of the library template cx-cx (2a_2).
"""
qr = QuantumRegister(2, "qr")
circuit_in = QuantumCircuit(qr)
circuit_in.h(qr[0])
circuit_in.h(qr[0])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[1], qr[0])
circuit_in.cx(qr[1], qr[0])
dag_in = circuit_to_dag(circuit_in)
template_list = [template_nct_2a_2()]
pass_ = TemplateOptimization(template_list)
dag_opt = pass_.run(dag_in)
circuit_expected = QuantumCircuit(qr)
circuit_expected.h(qr[0])
circuit_expected.h(qr[0])
dag_expected = circuit_to_dag(circuit_expected)
self.assertEqual(dag_opt, dag_expected)
def test_pass_template_nct_5a(self):
"""
Verify the result of template matching and substitution with the template 5a_3.
q_0: ───────■─────────■────■──
┌─┴─┐ ┌─┴─┐ │
q_1: ──■──┤ X ├──■──┤ X ├──┼──
┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐
q_2: ┤ X ├─────┤ X ├─────┤ X ├
└───┘ └───┘ └───┘
The circuit before optimization is:
┌───┐ ┌───┐
qr_0: ┤ X ├───────────────┤ X ├─────
└─┬─┘ ┌───┐┌───┐└─┬─┘
qr_1: ──┼────■──┤ X ├┤ Z ├──┼────■──
│ │ └─┬─┘└───┘ │ │
qr_2: ──┼────┼────■────■────■────┼──
│ │ ┌───┐┌─┴─┐ │ │
qr_3: ──■────┼──┤ H ├┤ X ├──■────┼──
│ ┌─┴─┐└───┘└───┘ ┌─┴─┐
qr_4: ──■──┤ X ├───────────────┤ X ├
└───┘ └───┘
The match is given by [0,1][1,2][2,7], after substitution the circuit becomes:
┌───┐ ┌───┐
qr_0: ┤ X ├───────────────┤ X ├
└─┬─┘ ┌───┐┌───┐└─┬─┘
qr_1: ──┼───────┤ X ├┤ Z ├──┼──
│ └─┬─┘└───┘ │
qr_2: ──┼────■────■────■────■──
│ │ ┌───┐┌─┴─┐ │
qr_3: ──■────┼──┤ H ├┤ X ├──■──
│ ┌─┴─┐└───┘└───┘
qr_4: ──■──┤ X ├───────────────
└───┘
"""
qr = QuantumRegister(5, "qr")
circuit_in = QuantumCircuit(qr)
circuit_in.ccx(qr[3], qr[4], qr[0])
circuit_in.cx(qr[1], qr[4])
circuit_in.cx(qr[2], qr[1])
circuit_in.h(qr[3])
circuit_in.z(qr[1])
circuit_in.cx(qr[2], qr[3])
circuit_in.ccx(qr[2], qr[3], qr[0])
circuit_in.cx(qr[1], qr[4])
dag_in = circuit_to_dag(circuit_in)
template_list = [template_nct_5a_3()]
pass_ = TemplateOptimization(template_list)
dag_opt = pass_.run(dag_in)
# note: cx(2, 1) commutes both with ccx(3, 4, 0) and with cx(2, 4),
# so there is no real difference with the circuit drawn on the picture above.
circuit_expected = QuantumCircuit(qr)
circuit_expected.cx(qr[2], qr[1])
circuit_expected.ccx(qr[3], qr[4], qr[0])
circuit_expected.cx(qr[2], qr[4])
circuit_expected.z(qr[1])
circuit_expected.h(qr[3])
circuit_expected.cx(qr[2], qr[3])
circuit_expected.ccx(qr[2], qr[3], qr[0])
dag_expected = circuit_to_dag(circuit_expected)
self.assertEqual(dag_opt, dag_expected)
def test_pass_template_wrong_type(self):
"""
If a template is not equivalent to the identity, it raises an error.
"""
qr = QuantumRegister(2, "qr")
circuit_in = QuantumCircuit(qr)
circuit_in.h(qr[0])
circuit_in.h(qr[0])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[0], qr[1])
circuit_in.cx(qr[1], qr[0])
circuit_in.cx(qr[1], qr[0])
dag_in = circuit_to_dag(circuit_in)
qrt = QuantumRegister(2, "qrc")
qct = QuantumCircuit(qrt)
qct.cx(0, 1)
qct.x(0)
qct.h(1)
template_list = [qct]
pass_ = TemplateOptimization(template_list)
self.assertRaises(TranspilerError, pass_.run, dag_in)
def test_accept_dagdependency(self):
"""
Check that users can supply DAGDependency in the template list.
"""
circuit_in = QuantumCircuit(2)
circuit_in.cx(0, 1)
circuit_in.cx(0, 1)
templates = [circuit_to_dagdependency(circuit_in)]
pass_ = TemplateOptimization(template_list=templates)
circuit_out = PassManager(pass_).run(circuit_in)
# these are NOT equal if template optimization works
self.assertNotEqual(circuit_in, circuit_out)
# however these are equivalent if the operators are the same
self.assertTrue(Operator(circuit_in).equiv(circuit_out))
def test_parametric_template(self):
"""
Check matching where template has parameters.
┌───────────┐ ┌────────┐
q_0: ┤ P(-1.0*β) ├──■────────────■──┤0 ├
├───────────┤┌─┴─┐┌──────┐┌─┴─┐│ CU(2β)│
q_1: ┤ P(-1.0*β) ├┤ X ├┤ P(β) ├┤ X ├┤1 ├
└───────────┘└───┘└──────┘└───┘└────────┘
First test try match on
┌───────┐
q_0: ┤ P(-2) ├──■────────────■─────────────────────────────
├───────┤┌─┴─┐┌──────┐┌─┴─┐┌───────┐
q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(-3) ├──■────────────■──
├───────┤└───┘└──────┘└───┘└───────┘┌─┴─┐┌──────┐┌─┴─┐
q_2: ┤ P(-3) ├───────────────────────────┤ X ├┤ P(3) ├┤ X ├
└───────┘ └───┘└──────┘└───┘
Second test try match on
┌───────┐
q_0: ┤ P(-2) ├──■────────────■────────────────────────────
├───────┤┌─┴─┐┌──────┐┌─┴─┐┌──────┐
q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(3) ├──■────────────■──
└┬──────┤└───┘└──────┘└───┘└──────┘┌─┴─┐┌──────┐┌─┴─┐
q_2: ─┤ P(3) ├──────────────────────────┤ X ├┤ P(3) ├┤ X ├
└──────┘ └───┘└──────┘└───┘
"""
beta = Parameter("β")
template = QuantumCircuit(2)
template.p(-beta, 0)
template.p(-beta, 1)
template.cx(0, 1)
template.p(beta, 1)
template.cx(0, 1)
template.cu(0, 2.0 * beta, 0, 0, 0, 1)
def count_cx(qc):
"""Counts the number of CX gates for testing."""
return qc.count_ops().get("cx", 0)
circuit_in = QuantumCircuit(3)
circuit_in.p(-2, 0)
circuit_in.p(-2, 1)
circuit_in.cx(0, 1)
circuit_in.p(2, 1)
circuit_in.cx(0, 1)
circuit_in.p(-3, 1)
circuit_in.p(-3, 2)
circuit_in.cx(1, 2)
circuit_in.p(3, 2)
circuit_in.cx(1, 2)
pass_ = TemplateOptimization(
template_list=[template],
user_cost_dict={"cx": 6, "p": 0, "cu": 8},
)
circuit_out = PassManager(pass_).run(circuit_in)
np.testing.assert_almost_equal(Operator(circuit_out).data[3, 3], np.exp(-4.0j))
np.testing.assert_almost_equal(Operator(circuit_out).data[7, 7], np.exp(-10.0j))
self.assertEqual(count_cx(circuit_out), 0) # Two matches => no CX gates.
np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data)
circuit_in = QuantumCircuit(3)
circuit_in.p(-2, 0)
circuit_in.p(-2, 1)
circuit_in.cx(0, 1)
circuit_in.p(2, 1)
circuit_in.cx(0, 1)
circuit_in.p(3, 1)
circuit_in.p(3, 2)
circuit_in.cx(1, 2)
circuit_in.p(3, 2)
circuit_in.cx(1, 2)
pass_ = TemplateOptimization(
template_list=[template],
user_cost_dict={"cx": 6, "p": 0, "cu": 8},
)
circuit_out = PassManager(pass_).run(circuit_in)
# these are NOT equal if template optimization works
self.assertNotEqual(circuit_in, circuit_out)
# however these are equivalent if the operators are the same
self.assertTrue(Operator(circuit_in).equiv(circuit_out))
def test_output_symbolic_library_equal(self):
"""Test that the template matcher returns parameter expressions that use the same symbolic
library as the default; it should not coerce everything to Sympy when playing with the
`ParameterExpression` internals."""
a, b = Parameter("a"), Parameter("b")
template = QuantumCircuit(1)
template.p(a, 0)
template.p(-a, 0)
template.rz(a, 0)
template.rz(-a, 0)
circuit = QuantumCircuit(1)
circuit.p(-b, 0)
circuit.p(b, 0)
pass_ = TemplateOptimization(template_list=[template], user_cost_dict={"p": 100, "rz": 1})
out = pass_(circuit)
expected = QuantumCircuit(1)
expected.rz(-b, 0)
expected.rz(b, 0)
self.assertEqual(out, expected)
def symbolic_library(expr):
"""Get the symbolic library of the expression - 'sympy' or 'symengine'."""
return type(expr._symbol_expr).__module__.split(".")[0]
out_exprs = [expr for instruction in out.data for expr in instruction.operation.params]
self.assertEqual(
[symbolic_library(b)] * len(out_exprs), [symbolic_library(expr) for expr in out_exprs]
)
# Assert that the result still works with parametric assignment.
self.assertEqual(out.assign_parameters({b: 1.5}), expected.assign_parameters({b: 1.5}))
def test_optimizer_does_not_replace_unbound_partial_match(self):
"""
Test that partial matches with parameters will not raise errors.
This tests that if parameters are still in the temporary template after
_attempt_bind then they will not be used.
"""
beta = Parameter("β")
template = QuantumCircuit(2)
template.cx(1, 0)
template.cx(1, 0)
template.p(beta, 1)
template.cu(0, 0, 0, -beta, 0, 1)
circuit_in = QuantumCircuit(2)
circuit_in.cx(1, 0)
circuit_in.cx(1, 0)
pass_ = TemplateOptimization(
template_list=[template],
user_cost_dict={"cx": 6, "p": 0, "cu": 8},
)
circuit_out = PassManager(pass_).run(circuit_in)
# The template optimization should not have replaced anything, because
# that would require it to leave dummy parameters in place without
# binding them.
self.assertEqual(circuit_in, circuit_out)
def test_unbound_parameters_in_rzx_template(self):
"""
Test that rzx template ('zz2') functions correctly for a simple
circuit with an unbound ParameterExpression. This uses the same
Parameter (theta) as the template, so this also checks that template
substitution handle this correctly.
"""
theta = Parameter("ϴ")
circuit_in = QuantumCircuit(2)
circuit_in.cx(0, 1)
circuit_in.p(2 * theta, 1)
circuit_in.cx(0, 1)
pass_ = TemplateOptimization(**rzx_templates(["zz2"]))
circuit_out = PassManager(pass_).run(circuit_in)
# these are NOT equal if template optimization works
self.assertNotEqual(circuit_in, circuit_out)
# however these are equivalent if the operators are the same
theta_set = 0.42
self.assertTrue(
Operator(circuit_in.assign_parameters({theta: theta_set})).equiv(
circuit_out.assign_parameters({theta: theta_set})
)
)
def test_two_parameter_template(self):
"""
Test a two-Parameter template based on rzx_templates(["zz3"]),
┌───┐┌───────┐┌───┐┌────────────┐»
q_0: ──■─────────────■──┤ X ├┤ Rz(φ) ├┤ X ├┤ Rz(-1.0*φ) ├»
┌─┴─┐┌───────┐┌─┴─┐└─┬─┘└───────┘└─┬─┘└────────────┘»
q_1: ┤ X ├┤ Rz(θ) ├┤ X ├──■─────────────■────────────────»
└───┘└───────┘└───┘
« ┌─────────┐┌─────────┐┌─────────┐┌───────────┐┌──────────────┐»
«q_0: ┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*φ) ├┤1 ├»
« └─────────┘└─────────┘└─────────┘└───────────┘│ Rzx(-1.0*φ) │»
«q_1: ──────────────────────────────────────────────┤0 ├»
« └──────────────┘»
« ┌─────────┐ ┌─────────┐┌─────────┐ »
«q_0: ─┤ Rz(π/2) ├──┤ Rx(π/2) ├┤ Rz(π/2) ├────────────────────────»
« ┌┴─────────┴─┐├─────────┤├─────────┤┌─────────┐┌───────────┐»
«q_1: ┤ Rz(-1.0*θ) ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*θ) ├»
« └────────────┘└─────────┘└─────────┘└─────────┘└───────────┘»
« ┌──────────────┐
«q_0: ┤0 ├─────────────────────────────────
« │ Rzx(-1.0*θ) │┌─────────┐┌─────────┐┌─────────┐
«q_1: ┤1 ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├
« └──────────────┘└─────────┘└─────────┘└─────────┘
correctly template matches into a unique circuit, but that it is
equivalent to the input circuit when the Parameters are bound to floats
and checked with Operator equivalence.
"""
theta = Parameter("θ")
phi = Parameter("φ")
template = QuantumCircuit(2)
template.cx(0, 1)
template.rz(theta, 1)
template.cx(0, 1)
template.cx(1, 0)
template.rz(phi, 0)
template.cx(1, 0)
template.rz(-phi, 0)
template.rz(np.pi / 2, 0)
template.rx(np.pi / 2, 0)
template.rz(np.pi / 2, 0)
template.rx(phi, 0)
template.rzx(-phi, 1, 0)
template.rz(np.pi / 2, 0)
template.rz(-theta, 1)
template.rx(np.pi / 2, 0)
template.rz(np.pi / 2, 1)
template.rz(np.pi / 2, 0)
template.rx(np.pi / 2, 1)
template.rz(np.pi / 2, 1)
template.rx(theta, 1)
template.rzx(-theta, 0, 1)
template.rz(np.pi / 2, 1)
template.rx(np.pi / 2, 1)
template.rz(np.pi / 2, 1)
alpha = Parameter("$\\alpha$")
beta = Parameter("$\\beta$")
circuit_in = QuantumCircuit(2)
circuit_in.cx(0, 1)
circuit_in.rz(2 * alpha, 1)
circuit_in.cx(0, 1)
circuit_in.cx(1, 0)
circuit_in.rz(3 * beta, 0)
circuit_in.cx(1, 0)
pass_ = TemplateOptimization(
[template],
user_cost_dict={"cx": 6, "rz": 0, "rx": 1, "rzx": 0},
)
circuit_out = PassManager(pass_).run(circuit_in)
# these are NOT equal if template optimization works
self.assertNotEqual(circuit_in, circuit_out)
# however these are equivalent if the operators are the same
alpha_set = 0.37
beta_set = 0.42
self.assertTrue(
Operator(circuit_in.assign_parameters({alpha: alpha_set, beta: beta_set})).equiv(
circuit_out.assign_parameters({alpha: alpha_set, beta: beta_set})
)
)
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)
def test_template_match_multiparameter(self):
"""Test that the template matching works on instructions that take more than one
parameter."""
a = Parameter("a")
b = Parameter("b")
template = QuantumCircuit(1)
template.u(0, a, b, 0)
template.rz(-a - b, 0)
circuit_in = QuantumCircuit(1)
circuit_in.u(0, 1.23, 2.45, 0)
pm = PassManager(TemplateOptimization([template], user_cost_dict={"u": 16, "rz": 0}))
circuit_out = pm.run(circuit_in)
expected = QuantumCircuit(1)
expected.rz(1.23 + 2.45, 0)
self.assertEqual(circuit_out, expected)
def test_naming_clash_multiparameter(self):
"""Test that the naming clash prevention mechanism works with instructions that take
multiple parameters."""
a_template = Parameter("a")
b_template = Parameter("b")
template = QuantumCircuit(1)
template.u(0, a_template, b_template, 0)
template.rz(-a_template - b_template, 0)
a_circuit = Parameter("a")
b_circuit = Parameter("b")
circuit_in = QuantumCircuit(1)
circuit_in.u(0, a_circuit, b_circuit, 0)
pm = PassManager(TemplateOptimization([template], user_cost_dict={"u": 16, "rz": 0}))
circuit_out = pm.run(circuit_in)
expected = QuantumCircuit(1)
expected.rz(a_circuit + b_circuit, 0)
self.assertEqual(circuit_out, expected)
def test_consecutive_templates_apply(self):
"""Test the scenario where one template optimization creates an opportunity for
another template optimization.
This is the original circuit:
┌───┐
q_0: ┤ X ├──■───X───────■─
└─┬─┘┌─┴─┐ │ ┌───┐ │
q_1: ──■──┤ X ├─X─┤ H ├─■─
└───┘ └───┘
The clifford_4_1 template allows to replace the two CNOTs followed by the SWAP by a
single CNOT:
q_0: ──■────────■─
┌─┴─┐┌───┐ │
q_1: ┤ X ├┤ H ├─■─
└───┘└───┘
At these point, the clifford_4_2 template allows to replace the circuit by a single
Hadamard gate:
q_0: ─────
┌───┐
q_1: ┤ H ├
└───┘
The second optimization would not have been possible without the applying the first
optimization.
"""
qc = QuantumCircuit(2)
qc.cx(1, 0)
qc.cx(0, 1)
qc.swap(0, 1)
qc.h(1)
qc.cz(0, 1)
qc_expected = QuantumCircuit(2)
qc_expected.h(1)
costs = {"h": 1, "cx": 2, "cz": 2, "swap": 3}
# Check that consecutively applying both templates leads to the expected circuit.
qc_opt = TemplateOptimization(
template_list=[clifford_4_1(), clifford_4_2()], user_cost_dict=costs
)(qc)
self.assertEqual(qc_opt, qc_expected)
# Also check that applying the second template by itself does not do anything.
qc_non_opt = TemplateOptimization(template_list=[clifford_4_2()], user_cost_dict=costs)(qc)
self.assertEqual(qc, qc_non_opt)
def test_consecutive_templates_do_not_apply(self):
"""Test that applying one template optimization does not allow incorrectly
applying other templates (which could happen if the DagDependency graph is
not constructed correctly after the optimization).
"""
template_list = [
clifford_2_2(),
clifford_2_3(),
]
pm = PassManager(TemplateOptimization(template_list=template_list))
qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.cx(0, 1)
qc.h(0)
qc.swap(0, 1)
qc.h(0)
qc_opt = pm.run(qc)
self.assertTrue(Operator(qc) == Operator(qc_opt))
def test_clifford_templates(self):
"""Tests TemplateOptimization pass on several larger examples."""
template_list = [
clifford_2_1(),
clifford_2_2(),
clifford_2_3(),
clifford_2_4(),
clifford_3_1(),
]
pm = PassManager(TemplateOptimization(template_list=template_list))
scc.clear_cached_commutations()
for seed in range(10):
qc = random_clifford_circuit(
num_qubits=5,
num_gates=100,
gates=["x", "y", "z", "h", "s", "sdg", "cx", "cz", "swap"],
seed=seed,
)
qc_opt = pm.run(qc)
self.assertTrue(Operator(qc) == Operator(qc_opt))
# All of these gates are in the commutation library, i.e. the cache should not be used
self.assertEqual(scc.num_cached_entries(), 0)
if __name__ == "__main__":
unittest.main()