mirror of https://github.com/Qiskit/qiskit.git
851 lines
31 KiB
Python
851 lines
31 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2017, 2018.
|
|
#
|
|
# 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 Qiskit's gates in QASM2."""
|
|
|
|
import unittest
|
|
from math import pi
|
|
import re
|
|
|
|
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
|
|
from qiskit.circuit import Parameter, Qubit, Clbit, Gate
|
|
from qiskit.circuit.library import C3SXGate, CCZGate, CSGate, CSdgGate, PermutationGate
|
|
from qiskit.qasm2.exceptions import QASM2Error as QasmError
|
|
from qiskit.qasm2 import dumps
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
# Regex pattern to match valid OpenQASM identifiers
|
|
VALID_QASM2_IDENTIFIER = re.compile("[a-z][a-zA-Z_0-9]*")
|
|
|
|
|
|
class TestCircuitQasm(QiskitTestCase):
|
|
"""QuantumCircuit QASM2 tests."""
|
|
|
|
def test_circuit_qasm(self):
|
|
"""Test circuit qasm() method."""
|
|
qr1 = QuantumRegister(1, "qr1")
|
|
qr2 = QuantumRegister(2, "qr2")
|
|
cr = ClassicalRegister(3, "cr")
|
|
qc = QuantumCircuit(qr1, qr2, cr)
|
|
qc.p(0.3, qr1[0])
|
|
qc.u(0.3, 0.2, 0.1, qr2[1])
|
|
qc.s(qr2[1])
|
|
qc.sdg(qr2[1])
|
|
qc.cx(qr1[0], qr2[1])
|
|
qc.barrier(qr2)
|
|
qc.cx(qr2[1], qr1[0])
|
|
qc.h(qr2[1])
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.x(qr2[1]).c_if(cr, 0)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.y(qr1[0]).c_if(cr, 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.z(qr1[0]).c_if(cr, 2)
|
|
qc.barrier(qr1, qr2)
|
|
qc.measure(qr1[0], cr[0])
|
|
qc.measure(qr2[0], cr[1])
|
|
qc.measure(qr2[1], cr[2])
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg qr1[1];
|
|
qreg qr2[2];
|
|
creg cr[3];
|
|
p(0.3) qr1[0];
|
|
u(0.3,0.2,0.1) qr2[1];
|
|
s qr2[1];
|
|
sdg qr2[1];
|
|
cx qr1[0],qr2[1];
|
|
barrier qr2[0],qr2[1];
|
|
cx qr2[1],qr1[0];
|
|
h qr2[1];
|
|
if(cr==0) x qr2[1];
|
|
if(cr==1) y qr1[0];
|
|
if(cr==2) z qr1[0];
|
|
barrier qr1[0],qr2[0],qr2[1];
|
|
measure qr1[0] -> cr[0];
|
|
measure qr2[0] -> cr[1];
|
|
measure qr2[1] -> cr[2];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_composite_circuit(self):
|
|
"""Test circuit qasm() method when a composite circuit instruction
|
|
is included within circuit.
|
|
"""
|
|
|
|
composite_circ_qreg = QuantumRegister(2)
|
|
composite_circ = QuantumCircuit(composite_circ_qreg, name="composite_circ")
|
|
composite_circ.h(0)
|
|
composite_circ.x(1)
|
|
composite_circ.cx(0, 1)
|
|
composite_circ_instr = composite_circ.to_instruction()
|
|
|
|
qr = QuantumRegister(2, "qr")
|
|
cr = ClassicalRegister(2, "cr")
|
|
qc = QuantumCircuit(qr, cr)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
qc.barrier()
|
|
qc.append(composite_circ_instr, [0, 1])
|
|
qc.measure([0, 1], [0, 1])
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate composite_circ q0,q1 { h q0; x q1; cx q0,q1; }
|
|
qreg qr[2];
|
|
creg cr[2];
|
|
h qr[0];
|
|
cx qr[0],qr[1];
|
|
barrier qr[0],qr[1];
|
|
composite_circ qr[0],qr[1];
|
|
measure qr[0] -> cr[0];
|
|
measure qr[1] -> cr[1];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_multiple_same_composite_circuits(self):
|
|
"""Test circuit qasm() method when a composite circuit is added
|
|
to the circuit multiple times
|
|
"""
|
|
|
|
composite_circ_qreg = QuantumRegister(2)
|
|
composite_circ = QuantumCircuit(composite_circ_qreg, name="composite_circ")
|
|
composite_circ.h(0)
|
|
composite_circ.x(1)
|
|
composite_circ.cx(0, 1)
|
|
composite_circ_instr = composite_circ.to_instruction()
|
|
|
|
qr = QuantumRegister(2, "qr")
|
|
cr = ClassicalRegister(2, "cr")
|
|
qc = QuantumCircuit(qr, cr)
|
|
qc.h(0)
|
|
qc.cx(0, 1)
|
|
qc.barrier()
|
|
qc.append(composite_circ_instr, [0, 1])
|
|
qc.append(composite_circ_instr, [0, 1])
|
|
qc.measure([0, 1], [0, 1])
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate composite_circ q0,q1 { h q0; x q1; cx q0,q1; }
|
|
qreg qr[2];
|
|
creg cr[2];
|
|
h qr[0];
|
|
cx qr[0],qr[1];
|
|
barrier qr[0],qr[1];
|
|
composite_circ qr[0],qr[1];
|
|
composite_circ qr[0],qr[1];
|
|
measure qr[0] -> cr[0];
|
|
measure qr[1] -> cr[1];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self):
|
|
"""Test circuit qasm() method when multiple composite circuit instructions
|
|
with the same circuit name are added to the circuit
|
|
"""
|
|
|
|
my_gate = QuantumCircuit(1, name="my_gate")
|
|
my_gate.h(0)
|
|
my_gate_inst1 = my_gate.to_instruction()
|
|
|
|
my_gate = QuantumCircuit(1, name="my_gate")
|
|
my_gate.x(0)
|
|
my_gate_inst2 = my_gate.to_instruction()
|
|
|
|
my_gate = QuantumCircuit(1, name="my_gate")
|
|
my_gate.x(0)
|
|
my_gate_inst3 = my_gate.to_instruction()
|
|
|
|
qr = QuantumRegister(1, name="qr")
|
|
circuit = QuantumCircuit(qr, name="circuit")
|
|
circuit.append(my_gate_inst1, [qr[0]])
|
|
circuit.append(my_gate_inst2, [qr[0]])
|
|
my_gate_inst2_id = id(circuit.data[-1].operation)
|
|
circuit.append(my_gate_inst3, [qr[0]])
|
|
my_gate_inst3_id = id(circuit.data[-1].operation)
|
|
# pylint: disable-next=consider-using-f-string
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate my_gate q0 {{ h q0; }}
|
|
gate my_gate_{1} q0 {{ x q0; }}
|
|
gate my_gate_{0} q0 {{ x q0; }}
|
|
qreg qr[1];
|
|
my_gate qr[0];
|
|
my_gate_{1} qr[0];
|
|
my_gate_{0} qr[0];""".format(
|
|
my_gate_inst3_id, my_gate_inst2_id
|
|
)
|
|
self.assertEqual(dumps(circuit), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(self):
|
|
"""Test circuit qasm() method when composite circuits with children
|
|
composite circuits in the definitions are added to the circuit"""
|
|
|
|
child_circ = QuantumCircuit(2, name="child_circ")
|
|
child_circ.h(0)
|
|
child_circ.cx(0, 1)
|
|
|
|
parent_circ = QuantumCircuit(3, name="parent_circ")
|
|
parent_circ.append(child_circ, range(2))
|
|
parent_circ.h(2)
|
|
|
|
grandparent_circ = QuantumCircuit(4, name="grandparent_circ")
|
|
grandparent_circ.append(parent_circ, range(3))
|
|
grandparent_circ.x(3)
|
|
|
|
qc = QuantumCircuit(4)
|
|
qc.append(grandparent_circ, range(4))
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate child_circ q0,q1 { h q0; cx q0,q1; }
|
|
gate parent_circ q0,q1,q2 { child_circ q0,q1; h q2; }
|
|
gate grandparent_circ q0,q1,q2,q3 { parent_circ q0,q1,q2; x q3; }
|
|
qreg q[4];
|
|
grandparent_circ q[0],q[1],q[2],q[3];"""
|
|
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_pi(self):
|
|
"""Test circuit qasm() method with pi params."""
|
|
circuit = QuantumCircuit(2)
|
|
circuit.cz(0, 1)
|
|
circuit.u(2 * pi, 3 * pi, -5 * pi, 0)
|
|
qasm_str = dumps(circuit)
|
|
circuit2 = QuantumCircuit.from_qasm_str(qasm_str)
|
|
self.assertEqual(circuit, circuit2)
|
|
|
|
def test_circuit_qasm_with_composite_circuit_with_one_param(self):
|
|
"""Test circuit qasm() method when a composite circuit instruction
|
|
has one param
|
|
"""
|
|
original_str = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate nG0(param0) q0 { h q0; }
|
|
qreg q[3];
|
|
creg c[3];
|
|
nG0(pi) q[0];"""
|
|
qc = QuantumCircuit.from_qasm_str(original_str)
|
|
|
|
self.assertEqual(original_str, dumps(qc))
|
|
|
|
def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self):
|
|
"""Test circuit qasm() method when a composite circuit instruction
|
|
has many params and qubits
|
|
"""
|
|
original_str = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate nG0(param0,param1) q0,q1 { h q0; h q1; }
|
|
qreg q[3];
|
|
qreg r[3];
|
|
creg c[3];
|
|
creg d[3];
|
|
nG0(pi,pi/2) q[0],r[0];"""
|
|
qc = QuantumCircuit.from_qasm_str(original_str)
|
|
|
|
self.assertEqual(original_str, dumps(qc))
|
|
|
|
def test_c3sxgate_roundtrips(self):
|
|
"""Test that C3SXGate correctly round trips.
|
|
|
|
Qiskit gives this gate a different name
|
|
('c3sx') to the name in Qiskit's version of qelib1.inc ('c3sqrtx') gate, which can lead to
|
|
resolution issues."""
|
|
qc = QuantumCircuit(4)
|
|
qc.append(C3SXGate(), qc.qubits, [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[4];
|
|
c3sqrtx q[0],q[1],q[2],q[3];"""
|
|
self.assertEqual(qasm, expected)
|
|
parsed = QuantumCircuit.from_qasm_str(qasm)
|
|
self.assertIsInstance(parsed.data[0].operation, C3SXGate)
|
|
|
|
def test_cczgate_qasm(self):
|
|
"""Test that CCZ dumps definition as a non-qelib1 gate."""
|
|
qc = QuantumCircuit(3)
|
|
qc.append(CCZGate(), qc.qubits, [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; }
|
|
qreg q[3];
|
|
ccz q[0],q[1],q[2];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_csgate_qasm(self):
|
|
"""Test that CS dumps definition as a non-qelib1 gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.append(CSGate(), qc.qubits, [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; }
|
|
qreg q[2];
|
|
cs q[0],q[1];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_csdggate_qasm(self):
|
|
"""Test that CSdg dumps definition as a non-qelib1 gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.append(CSdgGate(), qc.qubits, [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate csdg q0,q1 { p(-pi/4) q0; cx q0,q1; p(pi/4) q1; cx q0,q1; p(-pi/4) q1; }
|
|
qreg q[2];
|
|
csdg q[0],q[1];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_rzxgate_qasm(self):
|
|
"""Test that RZX dumps definition as a non-qelib1 gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.rzx(0, 0, 1)
|
|
qc.rzx(pi / 2, 1, 0)
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; }
|
|
qreg q[2];
|
|
rzx(0) q[0],q[1];
|
|
rzx(pi/2) q[1],q[0];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_ecrgate_qasm(self):
|
|
"""Test that ECR dumps its definition as a non-qelib1 gate."""
|
|
qc = QuantumCircuit(2)
|
|
qc.ecr(0, 1)
|
|
qc.ecr(1, 0)
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; }
|
|
gate ecr q0,q1 { rzx(pi/4) q0,q1; x q0; rzx(-pi/4) q0,q1; }
|
|
qreg q[2];
|
|
ecr q[0],q[1];
|
|
ecr q[1],q[0];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_unitary_qasm(self):
|
|
"""Test that UnitaryGate can be dumped to OQ2 correctly."""
|
|
qc = QuantumCircuit(1)
|
|
qc.unitary([[1, 0], [0, 1]], 0)
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate unitary q0 { u(0,0,0) q0; }
|
|
qreg q[1];
|
|
unitary q[0];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_multiple_unitary_qasm(self):
|
|
"""Test that multiple UnitaryGate instances can all dump successfully."""
|
|
custom = QuantumCircuit(1, name="custom")
|
|
custom.unitary([[1, 0], [0, -1]], 0)
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.unitary([[1, 0], [0, 1]], 0)
|
|
qc.unitary([[0, 1], [1, 0]], 1)
|
|
qc.append(custom.to_gate(), [0], [])
|
|
qasm = dumps(qc)
|
|
expected = re.compile(
|
|
r"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate unitary q0 { u\(0,0,0\) q0; }
|
|
gate (?P<u1>unitary_[0-9]*) q0 { u\(pi,-pi,0\) q0; }
|
|
gate (?P<u2>unitary_[0-9]*) q0 { u\(0,0,pi\) q0; }
|
|
gate custom q0 { (?P=u2) q0; }
|
|
qreg q\[2\];
|
|
unitary q\[0\];
|
|
(?P=u1) q\[1\];
|
|
custom q\[0\];""",
|
|
re.MULTILINE,
|
|
)
|
|
self.assertRegex(qasm, expected)
|
|
|
|
def test_unbound_circuit_raises(self):
|
|
"""Test circuits with unbound parameters raises."""
|
|
qc = QuantumCircuit(1)
|
|
theta = Parameter("θ")
|
|
qc.rz(theta, 0)
|
|
with self.assertRaises(QasmError):
|
|
dumps(qc)
|
|
|
|
def test_gate_qasm_with_ctrl_state(self):
|
|
"""Test gate qasm() with controlled gate that has ctrl_state setting."""
|
|
from qiskit.quantum_info import Operator
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.ch(0, 1, ctrl_state=0)
|
|
qasm_str = dumps(qc)
|
|
self.assertEqual(Operator(qc), Operator(QuantumCircuit.from_qasm_str(qasm_str)))
|
|
|
|
def test_circuit_qasm_with_mcx_gate(self):
|
|
"""Test circuit qasm() method with MCXGate
|
|
See https://github.com/Qiskit/qiskit-terra/issues/4943
|
|
"""
|
|
qc = QuantumCircuit(4)
|
|
qc.mcx([0, 1, 2], 3)
|
|
|
|
# qasm output doesn't support parameterized gate yet.
|
|
# param0 for "gate mcuq(param0) is not used inside the definition
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
|
|
qreg q[4];
|
|
mcx q[0],q[1],q[2],q[3];"""
|
|
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_mcx_gate_variants(self):
|
|
"""Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain"""
|
|
import qiskit.circuit.library as cl
|
|
|
|
n = 5
|
|
qc = QuantumCircuit(2 * n - 1)
|
|
qc.append(cl.MCXGrayCode(n), range(n + 1))
|
|
qc.append(cl.MCXRecursive(n), range(n + 2))
|
|
qc.append(cl.MCXVChain(n), range(2 * n - 1))
|
|
mcx_vchain_id = id(qc.data[-1].operation)
|
|
|
|
# qasm output doesn't support parameterized gate yet.
|
|
# param0 for "gate mcuq(param0) is not used inside the definition
|
|
expected_qasm = f"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate mcu1(param0) q0,q1,q2,q3,q4,q5 {{ cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }}
|
|
gate mcx_gray q0,q1,q2,q3,q4,q5 {{ h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }}
|
|
gate mcx_vchain q0,q1,q2,q3,q4 {{ h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }}
|
|
gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 {{ mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; }}
|
|
gate mcx_vchain_{mcx_vchain_id} q0,q1,q2,q3,q4,q5,q6,q7,q8 {{ rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }}
|
|
qreg q[9];
|
|
mcx_gray q[0],q[1],q[2],q[3],q[4],q[5];
|
|
mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6];
|
|
mcx_vchain_{mcx_vchain_id} q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];"""
|
|
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_registerless_bits(self):
|
|
"""Test that registerless bits do not have naming collisions in their registers."""
|
|
initial_registers = [QuantumRegister(2), ClassicalRegister(2)]
|
|
qc = QuantumCircuit(*initial_registers, [Qubit(), Clbit()])
|
|
# Match a 'qreg identifier[3];'-like QASM register declaration.
|
|
register_regex = re.compile(r"\s*[cq]reg\s+(\w+)\s*\[\d+\]\s*", re.M)
|
|
qasm_register_names = set()
|
|
for statement in dumps(qc).split(";"):
|
|
match = register_regex.match(statement)
|
|
if match:
|
|
qasm_register_names.add(match.group(1))
|
|
self.assertEqual(len(qasm_register_names), 4)
|
|
|
|
# Check that no additional registers were added to the circuit.
|
|
self.assertEqual(len(qc.qregs), 1)
|
|
self.assertEqual(len(qc.cregs), 1)
|
|
|
|
# Check that the registerless-register names are recalculated after adding more registers,
|
|
# to avoid naming clashes in this case.
|
|
generated_names = qasm_register_names - {register.name for register in initial_registers}
|
|
for generated_name in generated_names:
|
|
qc.add_register(QuantumRegister(1, name=generated_name))
|
|
qasm_register_names = set()
|
|
for statement in dumps(qc).split(";"):
|
|
match = register_regex.match(statement)
|
|
if match:
|
|
qasm_register_names.add(match.group(1))
|
|
self.assertEqual(len(qasm_register_names), 6)
|
|
|
|
def test_circuit_qasm_with_repeated_instruction_names(self):
|
|
"""Test that qasm() doesn't change the name of the instructions that live in circuit.data,
|
|
but a copy of them when there are repeated names."""
|
|
qc = QuantumCircuit(2)
|
|
qc.h(0)
|
|
qc.x(1)
|
|
# Create some random custom gate and name it "custom"
|
|
custom = QuantumCircuit(1)
|
|
custom.h(0)
|
|
custom.y(0)
|
|
gate = custom.to_gate()
|
|
gate.name = "custom"
|
|
# Another random custom gate named "custom" as well
|
|
custom2 = QuantumCircuit(2)
|
|
custom2.x(0)
|
|
custom2.z(1)
|
|
gate2 = custom2.to_gate()
|
|
gate2.name = "custom"
|
|
# Append custom gates with same name to original circuit
|
|
qc.append(gate, [0])
|
|
qc.append(gate2, [1, 0])
|
|
# Expected qasm string will append the id to the second gate with repeated name
|
|
expected_qasm = f"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate custom q0 {{ h q0; y q0; }}
|
|
gate custom_{id(gate2)} q0,q1 {{ x q0; z q1; }}
|
|
qreg q[2];
|
|
h q[0];
|
|
x q[1];
|
|
custom q[0];
|
|
custom_{id(gate2)} q[1],q[0];"""
|
|
# Check qasm() produced the correct string
|
|
self.assertEqual(expected_qasm, dumps(qc))
|
|
# Check instruction names were not changed by qasm()
|
|
names = ["h", "x", "custom", "custom"]
|
|
for idx, instruction in enumerate(qc._data):
|
|
self.assertEqual(instruction.operation.name, names[idx])
|
|
|
|
def test_circuit_qasm_with_invalid_identifiers(self):
|
|
"""Test that qasm() detects and corrects invalid OpenQASM gate identifiers,
|
|
while not changing the instructions on the original circuit"""
|
|
qc = QuantumCircuit(2)
|
|
|
|
# Create some gate and give it an invalid name
|
|
custom = QuantumCircuit(1)
|
|
custom.x(0)
|
|
custom.u(0, 0, pi, 0)
|
|
gate = custom.to_gate()
|
|
gate.name = "A[$]"
|
|
|
|
# Another gate also with invalid name
|
|
custom2 = QuantumCircuit(2)
|
|
custom2.x(0)
|
|
custom2.append(gate, [1])
|
|
gate2 = custom2.to_gate()
|
|
gate2.name = "invalid[name]"
|
|
|
|
# Append gates
|
|
qc.append(gate, [0])
|
|
qc.append(gate2, [1, 0])
|
|
|
|
# Expected qasm with valid identifiers
|
|
expected_qasm = "\n".join(
|
|
[
|
|
"OPENQASM 2.0;",
|
|
'include "qelib1.inc";',
|
|
"gate gate_A___ q0 { x q0; u(0,0,pi) q0; }",
|
|
"gate invalid_name_ q0,q1 { x q0; gate_A___ q1; }",
|
|
"qreg q[2];",
|
|
"gate_A___ q[0];",
|
|
"invalid_name_ q[1],q[0];",
|
|
]
|
|
)
|
|
|
|
# Check qasm() produces the correct string
|
|
self.assertEqual(expected_qasm, dumps(qc))
|
|
|
|
# Check instruction names were not changed by qasm()
|
|
names = ["A[$]", "invalid[name]"]
|
|
for idx, instruction in enumerate(qc._data):
|
|
self.assertEqual(instruction.operation.name, names[idx])
|
|
|
|
def test_circuit_qasm_with_duplicate_invalid_identifiers(self):
|
|
"""Test that qasm() corrects invalid identifiers and the de-duplication
|
|
code runs correctly, without altering original instructions"""
|
|
base = QuantumCircuit(1)
|
|
|
|
# First gate with invalid name, escapes to "invalid__"
|
|
clash1 = QuantumCircuit(1, name="invalid??")
|
|
clash1.x(0)
|
|
base.append(clash1, [0])
|
|
|
|
# Second gate with invalid name that also escapes to "invalid__"
|
|
clash2 = QuantumCircuit(1, name="invalid[]")
|
|
clash2.z(0)
|
|
base.append(clash2, [0])
|
|
|
|
# Check qasm is correctly produced
|
|
names = set()
|
|
for match in re.findall(r"gate (\S+)", dumps(base)):
|
|
self.assertTrue(VALID_QASM2_IDENTIFIER.fullmatch(match))
|
|
names.add(match)
|
|
self.assertEqual(len(names), 2)
|
|
|
|
# Check instruction names were not changed by qasm()
|
|
names = ["invalid??", "invalid[]"]
|
|
for idx, instruction in enumerate(base._data):
|
|
self.assertEqual(instruction.operation.name, names[idx])
|
|
|
|
def test_circuit_qasm_escapes_register_names(self):
|
|
"""Test that registers that have invalid OpenQASM 2 names get correctly escaped, even when
|
|
they would escape to the same value."""
|
|
qc = QuantumCircuit(QuantumRegister(2, "?invalid"), QuantumRegister(2, "!invalid"))
|
|
qc.cx(0, 1)
|
|
qc.cx(2, 3)
|
|
qasm = dumps(qc)
|
|
match = re.fullmatch(
|
|
rf"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\];
|
|
qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\];
|
|
cx \1\[0\],\1\[1\];
|
|
cx \2\[0\],\2\[1\];""",
|
|
qasm,
|
|
)
|
|
self.assertTrue(match)
|
|
self.assertNotEqual(match.group(1), match.group(2))
|
|
|
|
def test_circuit_qasm_escapes_reserved(self):
|
|
"""Test that the OpenQASM 2 exporter won't export reserved names."""
|
|
qc = QuantumCircuit(QuantumRegister(1, "qreg"))
|
|
gate = Gate("gate", 1, [])
|
|
gate.definition = QuantumCircuit(1)
|
|
qc.append(gate, [qc.qubits[0]])
|
|
qasm = dumps(qc)
|
|
match = re.fullmatch(
|
|
rf"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate ({VALID_QASM2_IDENTIFIER.pattern}) q0 {{ }}
|
|
qreg ({VALID_QASM2_IDENTIFIER.pattern})\[1\];
|
|
\1 \2\[0\];""",
|
|
qasm,
|
|
)
|
|
self.assertTrue(match)
|
|
self.assertNotEqual(match.group(1), "gate")
|
|
self.assertNotEqual(match.group(1), "qreg")
|
|
|
|
def test_circuit_qasm_with_double_precision_rotation_angle(self):
|
|
"""Test that qasm() emits high precision rotation angles per default."""
|
|
from qiskit.circuit.tools.pi_check import MAX_FRAC
|
|
|
|
qc = QuantumCircuit(1)
|
|
qc.p(0.123456789, 0)
|
|
qc.p(pi * pi, 0)
|
|
qc.p(MAX_FRAC * pi + 1, 0)
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[1];
|
|
p(0.123456789) q[0];
|
|
p(9.869604401089358) q[0];
|
|
p(51.26548245743669) q[0];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_qasm_with_rotation_angles_close_to_pi(self):
|
|
"""Test that qasm() properly rounds values closer than 1e-12 to pi."""
|
|
|
|
qc = QuantumCircuit(1)
|
|
qc.p(pi + 1e-11, 0)
|
|
qc.p(pi + 1e-12, 0)
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[1];
|
|
p(3.141592653599793) q[0];
|
|
p(pi) q[0];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_circuit_raises_on_single_bit_condition(self):
|
|
"""OpenQASM 2 can't represent single-bit conditions, so test that a suitable error is
|
|
printed if this is attempted."""
|
|
qc = QuantumCircuit(1, 1)
|
|
with self.assertWarns(DeprecationWarning):
|
|
qc.x(0).c_if(0, True)
|
|
|
|
with self.assertRaisesRegex(QasmError, "OpenQASM 2 can only condition on registers"):
|
|
dumps(qc)
|
|
|
|
def test_circuit_raises_invalid_custom_gate_no_qubits(self):
|
|
"""OpenQASM 2 exporter of custom gates with no qubits.
|
|
See: https://github.com/Qiskit/qiskit-terra/issues/10435"""
|
|
legit_circuit = QuantumCircuit(5, name="legit_circuit")
|
|
empty_circuit = QuantumCircuit(name="empty_circuit")
|
|
legit_circuit.append(empty_circuit)
|
|
|
|
with self.assertRaisesRegex(QasmError, "acts on zero qubits"):
|
|
dumps(legit_circuit)
|
|
|
|
def test_circuit_raises_invalid_custom_gate_clbits(self):
|
|
"""OpenQASM 2 exporter of custom instruction.
|
|
See: https://github.com/Qiskit/qiskit-terra/issues/7351"""
|
|
instruction = QuantumCircuit(2, 2, name="inst")
|
|
instruction.cx(0, 1)
|
|
instruction.measure([0, 1], [0, 1])
|
|
custom_instruction = instruction.to_instruction()
|
|
|
|
qc = QuantumCircuit(2, 2)
|
|
qc.append(custom_instruction, [0, 1], [0, 1])
|
|
|
|
with self.assertRaisesRegex(QasmError, "acts on 2 classical bits"):
|
|
dumps(qc)
|
|
|
|
def test_circuit_qasm_with_permutations(self):
|
|
"""Test circuit qasm() method with Permutation gates."""
|
|
|
|
qc = QuantumCircuit(4)
|
|
qc.append(PermutationGate([2, 1, 0]), [0, 1, 2])
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; }
|
|
qreg q[4];
|
|
permutation__2_1_0_ q[0],q[1],q[2];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_multiple_permutation(self):
|
|
"""Test that multiple PermutationGates can be added to a circuit."""
|
|
custom = QuantumCircuit(3, name="custom")
|
|
custom.append(PermutationGate([2, 1, 0]), [0, 1, 2])
|
|
custom.append(PermutationGate([0, 1, 2]), [0, 1, 2])
|
|
|
|
qc = QuantumCircuit(4)
|
|
qc.append(PermutationGate([2, 1, 0]), [0, 1, 2], [])
|
|
qc.append(PermutationGate([1, 2, 0]), [0, 1, 2], [])
|
|
qc.append(custom.to_gate(), [1, 3, 2], [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; }
|
|
gate permutation__1_2_0_ q0,q1,q2 { swap q1,q2; swap q0,q2; }
|
|
gate permutation__0_1_2_ q0,q1,q2 { }
|
|
gate custom q0,q1,q2 { permutation__2_1_0_ q0,q1,q2; permutation__0_1_2_ q0,q1,q2; }
|
|
qreg q[4];
|
|
permutation__2_1_0_ q[0],q[1],q[2];
|
|
permutation__1_2_0_ q[0],q[1],q[2];
|
|
custom q[1],q[3],q[2];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_circuit_qasm_with_reset(self):
|
|
"""Test circuit qasm() method with Reset."""
|
|
qc = QuantumCircuit(2)
|
|
qc.reset([0, 1])
|
|
|
|
expected_qasm = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[2];
|
|
reset q[0];
|
|
reset q[1];"""
|
|
self.assertEqual(dumps(qc), expected_qasm)
|
|
|
|
def test_nested_gate_naming_clashes(self):
|
|
"""Test that gates that have naming clashes but only appear in the body of another gate
|
|
still get exported correctly."""
|
|
|
|
# pylint: disable=missing-class-docstring
|
|
|
|
class Inner(Gate):
|
|
def __init__(self, param):
|
|
super().__init__("inner", 1, [param])
|
|
|
|
def _define(self):
|
|
self._definition = QuantumCircuit(1)
|
|
self._definition.rx(self.params[0], 0)
|
|
|
|
class Outer(Gate):
|
|
def __init__(self, param):
|
|
super().__init__("outer", 1, [param])
|
|
|
|
def _define(self):
|
|
self._definition = QuantumCircuit(1)
|
|
self._definition.append(Inner(self.params[0]), [0], [])
|
|
|
|
qc = QuantumCircuit(1)
|
|
qc.append(Outer(1.0), [0], [])
|
|
qc.append(Outer(2.0), [0], [])
|
|
qasm = dumps(qc)
|
|
|
|
expected = re.compile(
|
|
r"""OPENQASM 2\.0;
|
|
include "qelib1\.inc";
|
|
gate inner\(param0\) q0 { rx\(1\.0\) q0; }
|
|
gate outer\(param0\) q0 { inner\(1\.0\) q0; }
|
|
gate (?P<inner1>inner_[0-9]*)\(param0\) q0 { rx\(2\.0\) q0; }
|
|
gate (?P<outer1>outer_[0-9]*)\(param0\) q0 { (?P=inner1)\(2\.0\) q0; }
|
|
qreg q\[1\];
|
|
outer\(1\.0\) q\[0\];
|
|
(?P=outer1)\(2\.0\) q\[0\];""",
|
|
re.MULTILINE,
|
|
)
|
|
self.assertRegex(qasm, expected)
|
|
|
|
def test_opaque_output(self):
|
|
"""Test that gates with no definition are exported as `opaque`."""
|
|
custom = QuantumCircuit(1, name="custom")
|
|
custom.append(Gate("my_c", 1, []), [0])
|
|
|
|
qc = QuantumCircuit(2)
|
|
qc.append(Gate("my_a", 1, []), [0])
|
|
qc.append(Gate("my_a", 1, []), [1])
|
|
qc.append(Gate("my_b", 2, [1.0]), [1, 0])
|
|
qc.append(custom.to_gate(), [0], [])
|
|
qasm = dumps(qc)
|
|
expected = """OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
opaque my_a q0;
|
|
opaque my_b(param0) q0,q1;
|
|
opaque my_c q0;
|
|
gate custom q0 { my_c q0; }
|
|
qreg q[2];
|
|
my_a q[0];
|
|
my_a q[1];
|
|
my_b(1.0) q[1],q[0];
|
|
custom q[0];"""
|
|
self.assertEqual(qasm, expected)
|
|
|
|
def test_sequencial_inner_gates_with_same_name(self):
|
|
"""Test if inner gates sequentially added with the same name result in the correct qasm"""
|
|
qubits_range = range(3)
|
|
|
|
gate_a = QuantumCircuit(3, name="a")
|
|
gate_a.h(qubits_range)
|
|
gate_a = gate_a.to_instruction()
|
|
|
|
gate_b = QuantumCircuit(3, name="a")
|
|
gate_b.append(gate_a, qubits_range)
|
|
gate_b.x(qubits_range)
|
|
gate_b = gate_b.to_instruction()
|
|
|
|
qc = QuantumCircuit(3)
|
|
qc.append(gate_b, qubits_range)
|
|
qc.z(qubits_range)
|
|
|
|
gate_a_id = id(qc.data[0].operation)
|
|
|
|
expected_output = f"""OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
gate a q0,q1,q2 {{ h q0; h q1; h q2; }}
|
|
gate a_{gate_a_id} q0,q1,q2 {{ a q0,q1,q2; x q0; x q1; x q2; }}
|
|
qreg q[3];
|
|
a_{gate_a_id} q[0],q[1],q[2];
|
|
z q[0];
|
|
z q[1];
|
|
z q[2];"""
|
|
|
|
self.assertEqual(dumps(qc), expected_output)
|
|
|
|
def test_empty_barrier(self):
|
|
"""Test that a blank barrier statement in _Qiskit_ acts over all qubits, while an explicitly
|
|
no-op barrier (assuming Qiskit continues to allow this) is not output to OQ2 at all, since
|
|
the statement requires an argument in the spec."""
|
|
qc = QuantumCircuit(QuantumRegister(2, "qr1"), QuantumRegister(3, "qr2"))
|
|
qc.barrier() # In Qiskit land, this affects _all_ qubits.
|
|
qc.barrier([]) # This explicitly affects _no_ qubits (so is totally meaningless).
|
|
|
|
expected = """\
|
|
OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg qr1[2];
|
|
qreg qr2[3];
|
|
barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2];"""
|
|
self.assertEqual(dumps(qc), expected)
|
|
|
|
def test_small_angle_valid(self):
|
|
"""Test that small angles do not get converted to invalid OQ2 floating-point values."""
|
|
# OQ2 _technically_ requires a decimal point in all floating-point values, even ones that
|
|
# are followed by an exponent.
|
|
qc = QuantumCircuit(1)
|
|
qc.rx(0.000001, 0)
|
|
expected = """\
|
|
OPENQASM 2.0;
|
|
include "qelib1.inc";
|
|
qreg q[1];
|
|
rx(1.e-06) q[0];"""
|
|
self.assertEqual(dumps(qc), expected)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|