From b94f6104868ce7844d16bb5aa65d5c37e5465cf1 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Wed, 28 May 2025 21:15:59 +0800 Subject: [PATCH 01/13] - Fixes #7072 --- cirq-core/cirq/contrib/qasm_import/_parser.py | 52 ++++++++++++ .../cirq/contrib/qasm_import/_parser_test.py | 81 +++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 9432c0fa576..82dfa550f7f 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -229,6 +229,18 @@ def __init__(self) -> None: qelib_gates = { 'ccx': QasmGateStatement(qasm_gate='ccx', num_params=0, num_args=3, cirq_gate=ops.CCX), + 'c3x': QasmGateStatement( + qasm_gate='c3x', + num_params=0, + num_args=4, + cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=3) + ), + 'c4x': QasmGateStatement( + qasm_gate='c4x', + num_params=0, + num_args=5, + cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=4) + ), 'ch': QasmGateStatement( qasm_gate='ch', cirq_gate=ops.ControlledGate(ops.H), num_params=0, num_args=2 ), @@ -259,12 +271,34 @@ def __init__(self) -> None: num_args=2, cirq_gate=(lambda params: ops.ControlledGate(QasmUGate(0, 0, params[0] / np.pi))), ), + 'cu2': QasmGateStatement( + qasm_gate='cu2', + num_params=2, + num_args=2, + cirq_gate=( + lambda params: ops.ControlledGate( + QasmUGate(0.5, params[0] / np.pi, params[1] / np.pi) + ) + ), + ), 'cu3': QasmGateStatement( qasm_gate='cu3', num_params=3, num_args=2, cirq_gate=(lambda params: ops.ControlledGate(QasmUGate(*[p / np.pi for p in params]))), ), + 'cu': QasmGateStatement( + qasm_gate='cu', + num_params=3, + num_args=2, + cirq_gate=(lambda params: ops.ControlledGate(QasmUGate(*[p / np.pi for p in params]))), + ), + 'csx': QasmGateStatement( + qasm_gate='csx', + num_params=0, + num_args=2, + cirq_gate=ops.ControlledGate(ops.XPowGate(exponent=0.5)), + ), 'cx': QasmGateStatement(qasm_gate='cx', cirq_gate=CX, num_params=0, num_args=2), 'cy': QasmGateStatement( qasm_gate='cy', cirq_gate=ops.ControlledGate(ops.Y), num_params=0, num_args=2 @@ -325,12 +359,24 @@ def __init__(self) -> None: ), 't': QasmGateStatement(qasm_gate='t', num_params=0, num_args=1, cirq_gate=ops.T), 'tdg': QasmGateStatement(qasm_gate='tdg', num_params=0, num_args=1, cirq_gate=ops.T**-1), + 'u0': QasmGateStatement( + qasm_gate='u0', + cirq_gate=QasmUGate(0, 0, 0), + num_params=0, + num_args=1, + ), 'u1': QasmGateStatement( qasm_gate='u1', cirq_gate=(lambda params: QasmUGate(0, 0, params[0] / np.pi)), num_params=1, num_args=1, ), + 'p': QasmGateStatement( + qasm_gate='p', + cirq_gate=(lambda params: QasmUGate(0, 0, params[0] / np.pi)), + num_params=1, + num_args=1, + ), 'u2': QasmGateStatement( qasm_gate='u2', cirq_gate=(lambda params: QasmUGate(0.5, params[0] / np.pi, params[1] / np.pi)), @@ -343,6 +389,12 @@ def __init__(self) -> None: num_args=1, cirq_gate=(lambda params: QasmUGate(*[p / np.pi for p in params])), ), + 'u': QasmGateStatement( + qasm_gate='u', + num_params=3, + num_args=1, + cirq_gate=(lambda params: QasmUGate(*[p / np.pi for p in params])), + ), 'x': QasmGateStatement(qasm_gate='x', num_params=0, num_args=1, cirq_gate=ops.X), 'y': QasmGateStatement(qasm_gate='y', num_params=0, num_args=1, cirq_gate=ops.Y), 'z': QasmGateStatement(qasm_gate='z', num_params=0, num_args=1, cirq_gate=ops.Z), diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index c623733ee68..7479d5347a5 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -738,6 +738,27 @@ def test_reset() -> None: assert parsed_qasm.qregs == {'q': 1} assert parsed_qasm.cregs == {'c': 1} +def test_u0_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[1]; + u0 q[0]; +""" + parser = QasmParser() + + q0 = cirq.NamedQubit('q_0') + + expected_circuit = Circuit() + expected_circuit.append(QasmUGate(0, 0, 0)(q0)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 1} def test_u1_gate() -> None: qasm = """ @@ -762,6 +783,29 @@ def test_u1_gate() -> None: assert parsed_qasm.qregs == {'q': 1} +def test_p_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[1]; + p(pi / 3.0) q[0]; +""" + parser = QasmParser() + + q0 = cirq.NamedQubit('q_0') + + expected_circuit = Circuit() + expected_circuit.append(QasmUGate(0, 0, 1.0 / 3.0)(q0)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 1} + + def test_u2_gate() -> None: qasm = """ OPENQASM 2.0; @@ -844,6 +888,40 @@ def test_u3_gate() -> None: assert parsed_qasm.qregs == {'q': 2} +def test_u_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + u(pi, 2.3, 3) q[0]; + u(+3.14, -pi, (8)) q; +""" + parser = QasmParser() + + q0 = cirq.NamedQubit('q_0') + q1 = cirq.NamedQubit('q_1') + + expected_circuit = Circuit() + expected_circuit.append( + cirq.Moment( + [ + QasmUGate(1.0, 2.3 / np.pi, 3 / np.pi)(q0), + QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q1), + ] + ) + ) + + expected_circuit.append(cirq.Moment([QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q0)])) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 2} + + def test_r_gate() -> None: qasm = """ OPENQASM 2.0; @@ -924,13 +1002,16 @@ def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> N ('cy', cirq.ControlledGate(cirq.Y)), ('swap', cirq.SWAP), ('ch', cirq.ControlledGate(cirq.H)), + ('csx', cirq.ControlledGate(cirq.XPowGate(exponent=0.5))), ] # Mapping of two-qubit gates and `num_params` two_qubit_param_gates = { ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1 / np.pi))): 1, + ('cu2', cirq.ControlledGate(QasmUGate(0.5, 0.1/ np.pi, 0.2 / np.pi))): 2, ('cu3', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, + ('cu', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, ('crz', cirq.ControlledGate(cirq.rz(0.1))): 1, } From 4ede6606c1b7e009471e389d7a74d10c361139a2 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Thu, 29 May 2025 02:30:29 +0800 Subject: [PATCH 02/13] - Fixed tester issues. - Fixed `_parser.py` formatting issues. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 6 +- .../cirq/contrib/qasm_import/_parser_test.py | 58 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 82dfa550f7f..506ee922f8f 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -233,13 +233,13 @@ def __init__(self) -> None: qasm_gate='c3x', num_params=0, num_args=4, - cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=3) + cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=3), ), 'c4x': QasmGateStatement( qasm_gate='c4x', num_params=0, num_args=5, - cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=4) + cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=4), ), 'ch': QasmGateStatement( qasm_gate='ch', cirq_gate=ops.ControlledGate(ops.H), num_params=0, num_args=2 @@ -363,7 +363,7 @@ def __init__(self) -> None: qasm_gate='u0', cirq_gate=QasmUGate(0, 0, 0), num_params=0, - num_args=1, + num_args=1 ), 'u1': QasmGateStatement( qasm_gate='u1', diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 7479d5347a5..4f74b06dff6 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -1009,9 +1009,11 @@ def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> N # Mapping of two-qubit gates and `num_params` two_qubit_param_gates = { ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1 / np.pi))): 1, - ('cu2', cirq.ControlledGate(QasmUGate(0.5, 0.1/ np.pi, 0.2 / np.pi))): 2, + ('cu2', cirq.ControlledGate(QasmUGate(0.5, 0.1 / np.pi, 0.2 / np.pi))): 2, ('cu3', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, ('cu', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, + ('crx', cirq.ControlledGate(cirq.rx(0.1))): 1, + ('cry', cirq.ControlledGate(cirq.ry(0.1))): 1, ('crz', cirq.ControlledGate(cirq.rz(0.1))): 1, } @@ -1063,7 +1065,13 @@ def test_two_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) - def test_two_qubit_param_gates( qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate, num_params: int ) -> None: - params = '(0.1, 0.2, 0.3)' if num_params == 3 else '(0.1)' + if num_params == 1: + params = '(0.1)' + elif num_params == 2: + params = '(0.1, 0.2)' + elif num_params == 3: + params = '(0.1, 0.2, 0.3)' + qasm = f""" OPENQASM 2.0; include "qelib1.inc"; @@ -1099,26 +1107,23 @@ def test_two_qubit_param_gates( 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] ) def test_two_qubit_gates_not_enough_qubits(qasm_gate: str) -> None: - if qasm_gate in ('cu1', 'crz'): - qasm = f""" - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - {qasm_gate}(0.1) q[0]; - """ - elif qasm_gate == 'cu3': - qasm = f""" - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - {qasm_gate}(0.1, 0.2, 0.3) q[0]; - """ - else: - qasm = f""" + gate_mapping = { + 'crx': '(0.1)', + 'cry': '(0.1)', + 'crz': '(0.1)', + 'cu1': '(0.1)', + 'cu2': '(0.1, 0.2)', + 'cu3': '(0.1, 0.2, 0.3)', + 'cu': '(0.1, 0.2, 0.3)', + } + + qasm_param = gate_mapping.get(qasm_gate, '') + + qasm = f""" OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; - {qasm_gate} q[0]; + {qasm_gate}{qasm_param} q[0]; """ parser = QasmParser() @@ -1146,10 +1151,17 @@ def test_two_qubit_gates_not_enough_args(qasm_gate: str) -> None: 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] ) def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: - if qasm_gate in ('cu1', 'cu3', 'crz'): - num_params_needed = 3 if qasm_gate == 'cu3' else 1 - else: - num_params_needed = 0 + params_mapping = { + 'crx': 1, + 'cry': 1, + 'crz': 1, + 'cu1': 1, + 'cu2': 2, + 'cu3': 3, + 'cu': 3, + } + + num_params_needed = params_mapping.get(qasm_gate, 0) qasm = f""" OPENQASM 2.0; From 207504bb22146027a39c77be2f836debffc55f6e Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Thu, 29 May 2025 14:04:08 +0800 Subject: [PATCH 03/13] - Fixed formatting issues. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 5 +---- cirq-core/cirq/contrib/qasm_import/_parser_test.py | 12 +++--------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 506ee922f8f..78ebe7f8365 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -360,10 +360,7 @@ def __init__(self) -> None: 't': QasmGateStatement(qasm_gate='t', num_params=0, num_args=1, cirq_gate=ops.T), 'tdg': QasmGateStatement(qasm_gate='tdg', num_params=0, num_args=1, cirq_gate=ops.T**-1), 'u0': QasmGateStatement( - qasm_gate='u0', - cirq_gate=QasmUGate(0, 0, 0), - num_params=0, - num_args=1 + qasm_gate='u0', cirq_gate=QasmUGate(0, 0, 0), num_params=0, num_args=1 ), 'u1': QasmGateStatement( qasm_gate='u1', diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 4f74b06dff6..09c99dec45c 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -738,6 +738,7 @@ def test_reset() -> None: assert parsed_qasm.qregs == {'q': 1} assert parsed_qasm.cregs == {'c': 1} + def test_u0_gate() -> None: qasm = """ OPENQASM 2.0; @@ -760,6 +761,7 @@ def test_u0_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} + def test_u1_gate() -> None: qasm = """ OPENQASM 2.0; @@ -1151,15 +1153,7 @@ def test_two_qubit_gates_not_enough_args(qasm_gate: str) -> None: 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] ) def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: - params_mapping = { - 'crx': 1, - 'cry': 1, - 'crz': 1, - 'cu1': 1, - 'cu2': 2, - 'cu3': 3, - 'cu': 3, - } + params_mapping = {'crx': 1, 'cry': 1, 'crz': 1, 'cu1': 1, 'cu2': 2, 'cu3': 3, 'cu': 3,} num_params_needed = params_mapping.get(qasm_gate, 0) From 8f81a93b7b7922458e2dc104235cb236839b45f9 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Thu, 29 May 2025 14:23:25 +0800 Subject: [PATCH 04/13] - Fixed formatting. --- cirq-core/cirq/contrib/qasm_import/_parser_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 09c99dec45c..612a824202e 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -1153,7 +1153,7 @@ def test_two_qubit_gates_not_enough_args(qasm_gate: str) -> None: 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] ) def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: - params_mapping = {'crx': 1, 'cry': 1, 'crz': 1, 'cu1': 1, 'cu2': 2, 'cu3': 3, 'cu': 3,} + params_mapping = {'crx': 1, 'cry': 1, 'crz': 1, 'cu1': 1, 'cu2': 2, 'cu3': 3, 'cu': 3} num_params_needed = params_mapping.get(qasm_gate, 0) From d5cee33da6b0d69a171f81d0e8ddd70f63013321 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 7 Jun 2025 23:39:51 +0800 Subject: [PATCH 05/13] - Added `csx`, `c3sqrtx` and `cp` gates. - Added testers for `cp`, `c3x`, `c4x`, `csx`, and `c3sqrtx` gates. - Fixed u0 gate. - Removed `r`, `ryy`, `cu2`, and `iswap` as they are not part of qelib 1. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 45 +-- .../cirq/contrib/qasm_import/_parser_test.py | 339 ++++++++++++++---- 2 files changed, 276 insertions(+), 108 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 78ebe7f8365..519ae2b2403 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -262,6 +262,12 @@ def __init__(self) -> None: num_args=2, cirq_gate=(lambda params: ops.ControlledGate(ops.rz(params[0]))), ), + 'cp': QasmGateStatement( + qasm_gate='cp', + num_params=1, + num_args=2, + cirq_gate=(lambda params: ops.ControlledGate(ops.ZPowGate(exponent=params[0] / np.pi))), + ), 'cswap': QasmGateStatement( qasm_gate='cswap', num_params=0, num_args=3, cirq_gate=ops.CSWAP ), @@ -271,16 +277,6 @@ def __init__(self) -> None: num_args=2, cirq_gate=(lambda params: ops.ControlledGate(QasmUGate(0, 0, params[0] / np.pi))), ), - 'cu2': QasmGateStatement( - qasm_gate='cu2', - num_params=2, - num_args=2, - cirq_gate=( - lambda params: ops.ControlledGate( - QasmUGate(0.5, params[0] / np.pi, params[1] / np.pi) - ) - ), - ), 'cu3': QasmGateStatement( qasm_gate='cu3', num_params=3, @@ -299,6 +295,12 @@ def __init__(self) -> None: num_args=2, cirq_gate=ops.ControlledGate(ops.XPowGate(exponent=0.5)), ), + 'c3sqrtx': QasmGateStatement( + qasm_gate='c3sqrtx', + num_params=0, + num_args=4, + cirq_gate=ops.ControlledGate(ops.XPowGate(exponent=0.5), num_controls=3), + ), 'cx': QasmGateStatement(qasm_gate='cx', cirq_gate=CX, num_params=0, num_args=2), 'cy': QasmGateStatement( qasm_gate='cy', cirq_gate=ops.ControlledGate(ops.Y), num_params=0, num_args=2 @@ -306,20 +308,7 @@ def __init__(self) -> None: 'cz': QasmGateStatement(qasm_gate='cz', cirq_gate=ops.CZ, num_params=0, num_args=2), 'h': QasmGateStatement(qasm_gate='h', num_params=0, num_args=1, cirq_gate=ops.H), 'id': QasmGateStatement( - qasm_gate='id', cirq_gate=ops.IdentityGate(1), num_params=0, num_args=1 - ), - 'iswap': QasmGateStatement( - qasm_gate='iswap', cirq_gate=ops.ISwapPowGate(), num_params=0, num_args=2 - ), - 'r': QasmGateStatement( - qasm_gate='r', - num_params=2, - num_args=1, - cirq_gate=( - lambda params: QasmUGate( - params[0] / np.pi, (params[1] / np.pi) - 0.5, (-params[1] / np.pi) + 0.5 - ) - ), + qasm_gate='id', cirq_gate=ops.I, num_params=0, num_args=1 ), 'rx': QasmGateStatement( qasm_gate='rx', cirq_gate=(lambda params: ops.rx(params[0])), num_params=1, num_args=1 @@ -327,12 +316,6 @@ def __init__(self) -> None: 'ry': QasmGateStatement( qasm_gate='ry', cirq_gate=(lambda params: ops.ry(params[0])), num_params=1, num_args=1 ), - 'ryy': QasmGateStatement( - qasm_gate='ryy', - num_params=1, - num_args=2, - cirq_gate=(lambda params: ops.YYPowGate(exponent=params[0] / np.pi)), - ), 'rz': QasmGateStatement( qasm_gate='rz', cirq_gate=(lambda params: ops.rz(params[0])), num_params=1, num_args=1 ), @@ -360,7 +343,7 @@ def __init__(self) -> None: 't': QasmGateStatement(qasm_gate='t', num_params=0, num_args=1, cirq_gate=ops.T), 'tdg': QasmGateStatement(qasm_gate='tdg', num_params=0, num_args=1, cirq_gate=ops.T**-1), 'u0': QasmGateStatement( - qasm_gate='u0', cirq_gate=QasmUGate(0, 0, 0), num_params=0, num_args=1 + qasm_gate='u0', cirq_gate=(lambda _: ops.I), num_params=1, num_args=1 ), 'u1': QasmGateStatement( qasm_gate='u1', diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 90e1b4d6e29..cec94daaa67 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -216,6 +216,10 @@ def test_CX_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_classical_control() -> None: qasm = """OPENQASM 2.0; @@ -377,6 +381,10 @@ def test_U_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_U_angles() -> None: qasm = """ @@ -464,6 +472,10 @@ def test_expressions(expr: str) -> None: ) assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_unknown_function() -> None: qasm = """OPENQASM 2.0; @@ -476,10 +488,15 @@ def test_unknown_function() -> None: parser.parse(qasm) -rotation_gates = [('rx', cirq.rx), ('ry', cirq.ry), ('rz', cirq.rz)] +rotation_gates = [ + ('rx', cirq.rx), + ('ry', cirq.ry), + ('rz', cirq.rz), +] single_qubit_gates = [ + ('id', cirq.I), ('x', cirq.X), ('y', cirq.Y), ('z', cirq.Z), @@ -489,6 +506,7 @@ def test_unknown_function() -> None: ('sdg', cirq.S**-1), ('tdg', cirq.T**-1), ('sx', cirq.XPowGate(exponent=0.5)), + ('sxdg', cirq.XPowGate(exponent=-0.5)), ] @@ -518,6 +536,10 @@ def test_rotation_gates(qasm_gate: str, cirq_gate: Callable[[float], cirq.Gate]) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + @pytest.mark.parametrize('qasm_gate', [g[0] for g in rotation_gates]) def test_rotation_gates_wrong_number_of_args(qasm_gate: str) -> None: @@ -610,6 +632,10 @@ def test_measure_individual_bits() -> None: assert parsed_qasm.qregs == {'q1': 2} assert parsed_qasm.cregs == {'c1': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_measure_registers() -> None: qasm = """OPENQASM 2.0; @@ -639,6 +665,10 @@ def test_measure_registers() -> None: assert parsed_qasm.qregs == {'q1': 3} assert parsed_qasm.cregs == {'c1': 3} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_measure_mismatched_register_size() -> None: qasm = """OPENQASM 2.0; @@ -744,14 +774,14 @@ def test_u0_gate() -> None: OPENQASM 2.0; include "qelib1.inc"; qreg q[1]; - u0 q[0]; + u0(0) q[0]; """ parser = QasmParser() q0 = cirq.NamedQubit('q_0') expected_circuit = Circuit() - expected_circuit.append(QasmUGate(0, 0, 0)(q0)) + expected_circuit.append(cirq.I(q0)) parsed_qasm = parser.parse(qasm) @@ -761,6 +791,10 @@ def test_u0_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_u1_gate() -> None: qasm = """ @@ -784,6 +818,10 @@ def test_u1_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_p_gate() -> None: qasm = """ @@ -807,6 +845,10 @@ def test_p_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_u2_gate() -> None: qasm = """ @@ -830,6 +872,10 @@ def test_u2_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_id_gate() -> None: qasm = """ @@ -855,6 +901,10 @@ def test_id_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_u3_gate() -> None: qasm = """ @@ -889,6 +939,10 @@ def test_u3_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_u_gate() -> None: qasm = """ @@ -923,33 +977,14 @@ def test_u_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - -def test_r_gate() -> None: - qasm = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[1]; - r(pi, pi / 2.0) q[0]; -""" - parser = QasmParser() - - q0 = cirq.NamedQubit('q_0') - - expected_circuit = Circuit() - expected_circuit.append(QasmUGate(1.0, 0.0, 0.0)(q0)) - - parsed_qasm = parser.parse(qasm) - - assert parsed_qasm.supportedFormat - assert parsed_qasm.qelib1Include - - ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) - assert parsed_qasm.qregs == {'q': 1} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) @pytest.mark.parametrize( 'qasm_gate', - ['id', 'u2', 'u3', 'r'] + [g[0] for g in rotation_gates] + [g[0] for g in single_qubit_gates], + ['p', 'u0', 'u1', 'u2', 'u3'] + [g[0] for g in rotation_gates] + [g[0] for g in single_qubit_gates], ) def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate) -> None: qasm = f""" @@ -967,7 +1002,11 @@ def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate) -> None: @pytest.mark.parametrize( ['qasm_gate', 'num_params'], - [['id', 0], ['u2', 2], ['u3', 3], ['rx', 1], ['ry', 1], ['rz', 1], ['r', 2]] + [ + ['u0', 1], ['rx', 1], ['ry', 1], + ['rz', 1], ['p', 1], ['u1', 1], + ['u2', 2], ['u3', 3], ['u', 3] + ] + [[g[0], 0] for g in single_qubit_gates], ) def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> None: @@ -1010,13 +1049,13 @@ def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> N # Mapping of two-qubit gates and `num_params` two_qubit_param_gates = { - ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1 / np.pi))): 1, - ('cu2', cirq.ControlledGate(QasmUGate(0.5, 0.1 / np.pi, 0.2 / np.pi))): 2, - ('cu3', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, - ('cu', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, + # ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1))): 1, + # ('cu3', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, + # ('cu', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, ('crx', cirq.ControlledGate(cirq.rx(0.1))): 1, ('cry', cirq.ControlledGate(cirq.ry(0.1))): 1, ('crz', cirq.ControlledGate(cirq.rz(0.1))): 1, + ('cp', cirq.ControlledGate(cirq.ZPowGate(exponent=0.1/np.pi))): 1, } @@ -1056,6 +1095,10 @@ def test_two_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) - ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + @pytest.mark.parametrize( 'qasm_gate,cirq_gate,num_params', @@ -1104,6 +1147,10 @@ def test_two_qubit_param_gates( ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + @pytest.mark.parametrize( 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] @@ -1113,8 +1160,8 @@ def test_two_qubit_gates_not_enough_qubits(qasm_gate: str) -> None: 'crx': '(0.1)', 'cry': '(0.1)', 'crz': '(0.1)', + 'cp': '(0.1)', 'cu1': '(0.1)', - 'cu2': '(0.1, 0.2)', 'cu3': '(0.1, 0.2, 0.3)', 'cu': '(0.1, 0.2, 0.3)', } @@ -1153,7 +1200,7 @@ def test_two_qubit_gates_not_enough_args(qasm_gate: str) -> None: 'qasm_gate', [g[0] for g in two_qubit_gates] + [g[0] for g in two_qubit_param_gates.keys()] ) def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: - params_mapping = {'crx': 1, 'cry': 1, 'crz': 1, 'cu1': 1, 'cu2': 2, 'cu3': 3, 'cu': 3} + params_mapping = {'crx': 1, 'cry': 1, 'crz': 1, 'cp': 1, 'cu1': 1, 'cu3': 3, 'cu': 3} num_params_needed = params_mapping.get(qasm_gate, 0) @@ -1215,6 +1262,10 @@ def test_three_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + @pytest.mark.parametrize('qasm_gate', [g[0] for g in three_qubit_gates]) def test_three_qubit_gates_not_enough_args(qasm_gate: str) -> None: @@ -1244,6 +1295,170 @@ def test_three_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: parser.parse(qasm) +four_qubit_gates = [ + ('c3x', cirq.ControlledGate(cirq.X, num_controls=3)), + ('c3sqrtx', cirq.ControlledGate(cirq.XPowGate(exponent=0.5), num_controls=3)), +] + + +@pytest.mark.parametrize('qasm_gate,cirq_gate', four_qubit_gates) +def test_four_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) -> None: + qasm = f""" + OPENQASM 2.0; + include "qelib1.inc"; + qreg q1[2]; + qreg q2[2]; + qreg q3[2]; + qreg q4[2]; + {qasm_gate} q1[0], q1[1], q2[0], q3[0]; + {qasm_gate} q1, q2[0], q3[0], q4[0]; + {qasm_gate} q1, q2, q3, q4; +""" + parser = QasmParser() + + q1_0 = cirq.NamedQubit('q1_0') + q1_1 = cirq.NamedQubit('q1_1') + q2_0 = cirq.NamedQubit('q2_0') + q2_1 = cirq.NamedQubit('q2_1') + q3_0 = cirq.NamedQubit('q3_0') + q3_1 = cirq.NamedQubit('q3_1') + q4_0 = cirq.NamedQubit('q4_0') + q4_1 = cirq.NamedQubit('q4_1') + + expected_circuit = Circuit() + + expected_circuit.append(cirq_gate(q1_0, q1_1, q2_0, q3_0)) + + expected_circuit.append(cirq_gate(q1_0, q2_0, q3_0, q4_0)) + expected_circuit.append(cirq_gate(q1_1, q2_0, q3_0, q4_0)) + + expected_circuit.append(cirq_gate(q1_0, q2_0, q3_0, q4_0)) + expected_circuit.append(cirq_gate(q1_1, q2_1, q3_1, q4_1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2, 'q4': 2} + + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + + +@pytest.mark.parametrize('qasm_gate', [g[0] for g in four_qubit_gates]) +def test_four_qubit_gates_not_enough_args(qasm_gate: str) -> None: + qasm = f"""OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + {qasm_gate} q[0]; +""" + + parser = QasmParser() + + with pytest.raises(QasmException, match=rf".*{qasm_gate}.* takes 4 arg\(s\).*got.*1.*line 4"): + parser.parse(qasm) + + +@pytest.mark.parametrize('qasm_gate', [g[0] for g in four_qubit_gates]) +def test_four_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: + qasm = f"""OPENQASM 2.0; + include "qelib1.inc"; + qreg q[4]; + {qasm_gate}(pi) q[0],q[1],q[2],q[3]; +""" + + parser = QasmParser() + + with pytest.raises(QasmException, match=f".*{qasm_gate}.*parameter.*line 4.*"): + parser.parse(qasm) + + +five_qubit_gates = [ + ('c4x', cirq.ControlledGate(cirq.X, num_controls=4)), +] + + +@pytest.mark.parametrize('qasm_gate,cirq_gate', five_qubit_gates) +def test_five_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) -> None: + qasm = f""" + OPENQASM 2.0; + include "qelib1.inc"; + qreg q1[2]; + qreg q2[2]; + qreg q3[2]; + qreg q4[2]; + qreg q5[2]; + {qasm_gate} q1[0], q1[1], q2[0], q3[0], q4[0]; + {qasm_gate} q1, q2[0], q3[0], q4[0], q5[0]; + {qasm_gate} q1, q2, q3, q4, q5; +""" + parser = QasmParser() + + q1_0 = cirq.NamedQubit('q1_0') + q1_1 = cirq.NamedQubit('q1_1') + q2_0 = cirq.NamedQubit('q2_0') + q2_1 = cirq.NamedQubit('q2_1') + q3_0 = cirq.NamedQubit('q3_0') + q3_1 = cirq.NamedQubit('q3_1') + q4_0 = cirq.NamedQubit('q4_0') + q4_1 = cirq.NamedQubit('q4_1') + q5_0 = cirq.NamedQubit('q5_0') + q5_1 = cirq.NamedQubit('q5_1') + + expected_circuit = Circuit() + + expected_circuit.append(cirq_gate(q1_0, q1_1, q2_0, q3_0, q4_0)) + + expected_circuit.append(cirq_gate(q1_0, q2_0, q3_0, q4_0, q5_0)) + expected_circuit.append(cirq_gate(q1_1, q2_0, q3_0, q4_0, q5_0)) + + expected_circuit.append(cirq_gate(q1_0, q2_0, q3_0, q4_0, q5_0)) + expected_circuit.append(cirq_gate(q1_1, q2_1, q3_1, q4_1, q5_1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2, 'q4': 2, 'q5': 2} + + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + + +@pytest.mark.parametrize('qasm_gate', [g[0] for g in five_qubit_gates]) +def test_five_qubit_gates_not_enough_args(qasm_gate: str) -> None: + qasm = f"""OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + {qasm_gate} q[0]; +""" + + parser = QasmParser() + + with pytest.raises(QasmException, match=rf".*{qasm_gate}.* takes 5 arg\(s\).*got.*1.*line 4"): + parser.parse(qasm) + + +@pytest.mark.parametrize('qasm_gate', [g[0] for g in five_qubit_gates]) +def test_five_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: + qasm = f"""OPENQASM 2.0; + include "qelib1.inc"; + qreg q[5]; + {qasm_gate}(pi) q[0],q[1],q[2],q[3],q[4]; +""" + + parser = QasmParser() + + with pytest.raises(QasmException, match=f".*{qasm_gate}.*parameter.*line 4.*"): + parser.parse(qasm) + + @pytest.mark.parametrize('qasm_gate,cirq_gate', single_qubit_gates) def test_single_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: qasm = f"""OPENQASM 2.0; @@ -1268,6 +1483,10 @@ def test_single_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_openqasm_3_0_qubits() -> None: qasm = """OPENQASM 3.0; @@ -1625,6 +1844,10 @@ def test_rzz_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) + def test_rxx_gate() -> None: qasm = """ @@ -1648,28 +1871,9 @@ def test_rxx_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - -def test_ryy_gate() -> None: - qasm = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - ryy(pi/3) q[0],q[1]; - """ - parser = QasmParser() - - q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') - - expected_circuit = Circuit() - expected_circuit.append(cirq.YYPowGate(exponent=1 / 3).on(q0, q1)) - - parsed_qasm = parser.parse(qasm) - - assert parsed_qasm.supportedFormat - assert parsed_qasm.qelib1Include - - ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) - assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) def test_crx_gate() -> None: @@ -1694,28 +1898,9 @@ def test_crx_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - -def test_iswap_gate() -> None: - qasm = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[2]; - iswap q[0],q[1]; - """ - parser = QasmParser() - - q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') - - expected_circuit = Circuit() - expected_circuit.append(cirq.ISwapPowGate().on(q0, q1)) - - parsed_qasm = parser.parse(qasm) - - assert parsed_qasm.supportedFormat - assert parsed_qasm.qelib1Include - - ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) - assert parsed_qasm.qregs == {'q': 2} + cq.assert_qiskit_parsed_qasm_consistent_with_unitary( + qasm, cirq.unitary(expected_circuit) + ) @pytest.mark.parametrize( From be81e1748a5ed1ca0307ee38d2feff9d82e4efef Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 7 Jun 2025 23:43:58 +0800 Subject: [PATCH 06/13] - Fixes linting issues. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 4 +- .../cirq/contrib/qasm_import/_parser_test.py | 116 ++++++------------ 2 files changed, 38 insertions(+), 82 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 519ae2b2403..89f81713126 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -307,9 +307,7 @@ def __init__(self) -> None: ), 'cz': QasmGateStatement(qasm_gate='cz', cirq_gate=ops.CZ, num_params=0, num_args=2), 'h': QasmGateStatement(qasm_gate='h', num_params=0, num_args=1, cirq_gate=ops.H), - 'id': QasmGateStatement( - qasm_gate='id', cirq_gate=ops.I, num_params=0, num_args=1 - ), + 'id': QasmGateStatement(qasm_gate='id', cirq_gate=ops.I, num_params=0, num_args=1), 'rx': QasmGateStatement( qasm_gate='rx', cirq_gate=(lambda params: ops.rx(params[0])), num_params=1, num_args=1 ), diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index cec94daaa67..7b0a71d857e 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -216,9 +216,7 @@ def test_CX_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_classical_control() -> None: @@ -381,9 +379,7 @@ def test_U_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_U_angles() -> None: @@ -472,9 +468,7 @@ def test_expressions(expr: str) -> None: ) assert parsed_qasm.qregs == {'q': 1} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_unknown_function() -> None: @@ -488,11 +482,7 @@ def test_unknown_function() -> None: parser.parse(qasm) -rotation_gates = [ - ('rx', cirq.rx), - ('ry', cirq.ry), - ('rz', cirq.rz), -] +rotation_gates = [('rx', cirq.rx), ('ry', cirq.ry), ('rz', cirq.rz)] single_qubit_gates = [ @@ -536,9 +526,7 @@ def test_rotation_gates(qasm_gate: str, cirq_gate: Callable[[float], cirq.Gate]) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize('qasm_gate', [g[0] for g in rotation_gates]) @@ -632,9 +620,7 @@ def test_measure_individual_bits() -> None: assert parsed_qasm.qregs == {'q1': 2} assert parsed_qasm.cregs == {'c1': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_measure_registers() -> None: @@ -665,9 +651,7 @@ def test_measure_registers() -> None: assert parsed_qasm.qregs == {'q1': 3} assert parsed_qasm.cregs == {'c1': 3} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_measure_mismatched_register_size() -> None: @@ -791,9 +775,7 @@ def test_u0_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_u1_gate() -> None: @@ -818,9 +800,7 @@ def test_u1_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_p_gate() -> None: @@ -845,9 +825,7 @@ def test_p_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_u2_gate() -> None: @@ -872,9 +850,7 @@ def test_u2_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 1} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_id_gate() -> None: @@ -901,9 +877,7 @@ def test_id_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_u3_gate() -> None: @@ -939,9 +913,7 @@ def test_u3_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_u_gate() -> None: @@ -977,14 +949,14 @@ def test_u_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize( 'qasm_gate', - ['p', 'u0', 'u1', 'u2', 'u3'] + [g[0] for g in rotation_gates] + [g[0] for g in single_qubit_gates], + ['p', 'u0', 'u1', 'u2', 'u3'] + + [g[0] for g in rotation_gates] + + [g[0] for g in single_qubit_gates], ) def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate) -> None: qasm = f""" @@ -1003,9 +975,15 @@ def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate) -> None: @pytest.mark.parametrize( ['qasm_gate', 'num_params'], [ - ['u0', 1], ['rx', 1], ['ry', 1], - ['rz', 1], ['p', 1], ['u1', 1], - ['u2', 2], ['u3', 3], ['u', 3] + ['u0', 1], + ['rx', 1], + ['ry', 1], + ['rz', 1], + ['p', 1], + ['u1', 1], + ['u2', 2], + ['u3', 3], + ['u', 3], ] + [[g[0], 0] for g in single_qubit_gates], ) @@ -1055,7 +1033,7 @@ def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> N ('crx', cirq.ControlledGate(cirq.rx(0.1))): 1, ('cry', cirq.ControlledGate(cirq.ry(0.1))): 1, ('crz', cirq.ControlledGate(cirq.rz(0.1))): 1, - ('cp', cirq.ControlledGate(cirq.ZPowGate(exponent=0.1/np.pi))): 1, + ('cp', cirq.ControlledGate(cirq.ZPowGate(exponent=0.1 / np.pi))): 1, } @@ -1095,9 +1073,7 @@ def test_two_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) - ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize( @@ -1147,9 +1123,7 @@ def test_two_qubit_param_gates( ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize( @@ -1262,9 +1236,7 @@ def test_three_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize('qasm_gate', [g[0] for g in three_qubit_gates]) @@ -1343,9 +1315,7 @@ def test_four_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2, 'q4': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize('qasm_gate', [g[0] for g in four_qubit_gates]) @@ -1376,9 +1346,7 @@ def test_four_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: parser.parse(qasm) -five_qubit_gates = [ - ('c4x', cirq.ControlledGate(cirq.X, num_controls=4)), -] +five_qubit_gates = [('c4x', cirq.ControlledGate(cirq.X, num_controls=4))] @pytest.mark.parametrize('qasm_gate,cirq_gate', five_qubit_gates) @@ -1426,9 +1394,7 @@ def test_five_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q1': 2, 'q2': 2, 'q3': 2, 'q4': 2, 'q5': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize('qasm_gate', [g[0] for g in five_qubit_gates]) @@ -1483,9 +1449,7 @@ def test_single_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_openqasm_3_0_qubits() -> None: @@ -1844,9 +1808,7 @@ def test_rzz_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_rxx_gate() -> None: @@ -1871,9 +1833,7 @@ def test_rxx_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) def test_crx_gate() -> None: @@ -1898,9 +1858,7 @@ def test_crx_gate() -> None: ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2} - cq.assert_qiskit_parsed_qasm_consistent_with_unitary( - qasm, cirq.unitary(expected_circuit) - ) + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) @pytest.mark.parametrize( From 24a2d3f9e497bee27d478aa501293469129a5dfb Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Tue, 10 Jun 2025 08:51:05 +0000 Subject: [PATCH 07/13] - Brings back deleted gates [r, ryy, iswap] based on maintainer note. - Fixes gate definition for `id` to address git-blame note. - Fixes gate definition for `p` to use ZPowGate. - Adds rccx and rc3x using `cirq.MatrixGate`. - Adds missing testers for cry and crz. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 129 +++++++++- .../cirq/contrib/qasm_import/_parser_test.py | 229 +++++++++++++++++- 2 files changed, 353 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index 89f81713126..d1de957094d 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -229,12 +229,118 @@ def __init__(self) -> None: qelib_gates = { 'ccx': QasmGateStatement(qasm_gate='ccx', num_params=0, num_args=3, cirq_gate=ops.CCX), + 'rccx': QasmGateStatement( + qasm_gate='rccx', + num_params=0, + num_args=3, + cirq_gate=ops.MatrixGate( + np.array([ + [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j] + ]) + ) + ), 'c3x': QasmGateStatement( qasm_gate='c3x', num_params=0, num_args=4, cirq_gate=ops.ControlledGate(sub_gate=ops.X, num_controls=3), ), + 'rc3x': QasmGateStatement( + qasm_gate='rc3x', + num_params=0, + num_args=4, + cirq_gate=ops.MatrixGate( + np.array([ + [ + 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 1.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + -1.+0.j, 0.+0.j + ] + ]) + ) + ), 'c4x': QasmGateStatement( qasm_gate='c4x', num_params=0, @@ -307,7 +413,20 @@ def __init__(self) -> None: ), 'cz': QasmGateStatement(qasm_gate='cz', cirq_gate=ops.CZ, num_params=0, num_args=2), 'h': QasmGateStatement(qasm_gate='h', num_params=0, num_args=1, cirq_gate=ops.H), - 'id': QasmGateStatement(qasm_gate='id', cirq_gate=ops.I, num_params=0, num_args=1), + 'id': QasmGateStatement(qasm_gate='id', cirq_gate=ops.IdentityGate(1), num_params=0, num_args=1), + 'iswap': QasmGateStatement( + qasm_gate='iswap', cirq_gate=ops.ISwapPowGate(), num_params=0, num_args=2 + ), + 'r': QasmGateStatement( + qasm_gate='r', + num_params=2, + num_args=1, + cirq_gate=( + lambda params: QasmUGate( + params[0] / np.pi, (params[1] / np.pi) - 0.5, (-params[1] / np.pi) + 0.5 + ) + ), + ), 'rx': QasmGateStatement( qasm_gate='rx', cirq_gate=(lambda params: ops.rx(params[0])), num_params=1, num_args=1 ), @@ -323,6 +442,12 @@ def __init__(self) -> None: num_args=2, cirq_gate=(lambda params: ops.XXPowGate(exponent=params[0] / np.pi)), ), + 'ryy': QasmGateStatement( + qasm_gate='ryy', + num_params=1, + num_args=2, + cirq_gate=(lambda params: ops.YYPowGate(exponent=params[0] / np.pi)), + ), 'rzz': QasmGateStatement( qasm_gate='rzz', num_params=1, @@ -351,7 +476,7 @@ def __init__(self) -> None: ), 'p': QasmGateStatement( qasm_gate='p', - cirq_gate=(lambda params: QasmUGate(0, 0, params[0] / np.pi)), + cirq_gate=(lambda params: ops.ZPowGate(exponent=params[0] / np.pi)), num_params=1, num_args=1, ), diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 7b0a71d857e..5c42083fc3b 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -815,7 +815,7 @@ def test_p_gate() -> None: q0 = cirq.NamedQubit('q_0') expected_circuit = Circuit() - expected_circuit.append(QasmUGate(0, 0, 1.0 / 3.0)(q0)) + expected_circuit.append(cirq.ZPowGate(exponent=1.0 / 3.0)(q0)) parsed_qasm = parser.parse(qasm) @@ -952,9 +952,31 @@ def test_u_gate() -> None: cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) +def test_r_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[1]; + r(pi, pi / 2.0) q[0]; +""" + parser = QasmParser() + + q0 = cirq.NamedQubit('q_0') + + expected_circuit = Circuit() + expected_circuit.append(QasmUGate(1.0, 0.0, 0.0)(q0)) + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 1} + + @pytest.mark.parametrize( 'qasm_gate', - ['p', 'u0', 'u1', 'u2', 'u3'] + ['p', 'u0', 'u1', 'u2', 'r', 'u3'] + [g[0] for g in rotation_gates] + [g[0] for g in single_qubit_gates], ) @@ -982,6 +1004,7 @@ def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate) -> None: ['p', 1], ['u1', 1], ['u2', 2], + ['r', 2], ['u3', 3], ['u', 3], ] @@ -1194,7 +1217,25 @@ def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: parser.parse(qasm) -three_qubit_gates = [('ccx', cirq.TOFFOLI), ('cswap', cirq.CSWAP)] +three_qubit_gates = [ + ('ccx', cirq.TOFFOLI), + ('cswap', cirq.CSWAP), + ( + 'rccx', + cirq.MatrixGate( + np.array([ + [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j], + [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j] + ]) + ) + ), +] @pytest.mark.parametrize('qasm_gate,cirq_gate', three_qubit_gates) @@ -1270,6 +1311,93 @@ def test_three_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: four_qubit_gates = [ ('c3x', cirq.ControlledGate(cirq.X, num_controls=3)), ('c3sqrtx', cirq.ControlledGate(cirq.XPowGate(exponent=0.5), num_controls=3)), + ( + 'rc3x', + cirq.MatrixGate( + np.array([ + [ + 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, + 0.+0.j, 0.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 1.+0.j + ], + [ + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, + -1.+0.j, 0.+0.j + ] + ]) + ) + ) ] @@ -1811,6 +1939,28 @@ def test_rzz_gate() -> None: cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) +def test_ryy_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + ryy(pi/3) q[0],q[1]; + """ + parser = QasmParser() + + q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') + + expected_circuit = Circuit() + expected_circuit.append(cirq.YYPowGate(exponent=1 / 3).on(q0, q1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 2} + + def test_rxx_gate() -> None: qasm = """ OPENQASM 2.0; @@ -1861,6 +2011,79 @@ def test_crx_gate() -> None: cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) +def test_cry_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + cry(pi/7) q[0],q[1]; + """ + parser = QasmParser() + + q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') + + expected_circuit = Circuit() + expected_circuit.append(cirq.ControlledGate(cirq.ry(np.pi / 7)).on(q0, q1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 2} + + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) + + +def test_crz_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + crz(pi/7) q[0],q[1]; + """ + parser = QasmParser() + + q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') + + expected_circuit = Circuit() + expected_circuit.append(cirq.ControlledGate(cirq.rz(np.pi / 7)).on(q0, q1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 2} + + cq.assert_qiskit_parsed_qasm_consistent_with_unitary(qasm, cirq.unitary(expected_circuit)) + + +def test_iswap_gate() -> None: + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[2]; + iswap q[0],q[1]; + """ + parser = QasmParser() + + q0, q1 = cirq.NamedQubit('q_0'), cirq.NamedQubit('q_1') + + expected_circuit = Circuit() + expected_circuit.append(cirq.ISwapPowGate().on(q0, q1)) + + parsed_qasm = parser.parse(qasm) + + assert parsed_qasm.supportedFormat + assert parsed_qasm.qelib1Include + + ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) + assert parsed_qasm.qregs == {'q': 2} + + @pytest.mark.parametrize( "qasm_gate,cirq_gate,num_params,num_args", [ From f80eb8543f5c1e1bf9e4550714bb9a3245dcd56c Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:41:00 +0000 Subject: [PATCH 08/13] - Fixed format and lint issues. --- cirq-core/cirq/contrib/qasm_import/_parser.py | 472 ++++++++++++++---- .../cirq/contrib/qasm_import/_parser_test.py | 470 +++++++++++++---- 2 files changed, 756 insertions(+), 186 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser.py b/cirq-core/cirq/contrib/qasm_import/_parser.py index d1de957094d..bbc15caeb1e 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser.py @@ -234,17 +234,91 @@ def __init__(self) -> None: num_params=0, num_args=3, cirq_gate=ops.MatrixGate( - np.array([ - [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j] - ]) - ) + np.array( + [ + [ + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + -1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 - 1.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 1.0j, + 0.0 + 0.0j, + ], + ] + ) + ), ), 'c3x': QasmGateStatement( qasm_gate='c3x', @@ -257,89 +331,299 @@ def __init__(self) -> None: num_params=0, num_args=4, cirq_gate=ops.MatrixGate( - np.array([ - [ - 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], + np.array( [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 1.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - -1.+0.j, 0.+0.j + [ + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 1.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 - 1.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + -1.0 + 0.0j, + 0.0 + 0.0j, + ], ] - ]) - ) + ) + ), ), 'c4x': QasmGateStatement( qasm_gate='c4x', @@ -413,7 +697,9 @@ def __init__(self) -> None: ), 'cz': QasmGateStatement(qasm_gate='cz', cirq_gate=ops.CZ, num_params=0, num_args=2), 'h': QasmGateStatement(qasm_gate='h', num_params=0, num_args=1, cirq_gate=ops.H), - 'id': QasmGateStatement(qasm_gate='id', cirq_gate=ops.IdentityGate(1), num_params=0, num_args=1), + 'id': QasmGateStatement( + qasm_gate='id', cirq_gate=ops.IdentityGate(1), num_params=0, num_args=1 + ), 'iswap': QasmGateStatement( qasm_gate='iswap', cirq_gate=ops.ISwapPowGate(), num_params=0, num_args=2 ), diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 5c42083fc3b..8739aa73ca9 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -1223,17 +1223,91 @@ def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: ( 'rccx', cirq.MatrixGate( - np.array([ - [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j], - [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j] - ]) - ) + np.array( + [ + [ + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + -1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 - 1.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 1.0j, + 0.0 + 0.0j, + ], + ] + ) + ), ), ] @@ -1314,90 +1388,300 @@ def test_three_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: ( 'rc3x', cirq.MatrixGate( - np.array([ - [ - 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], + np.array( [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, - 0.+0.j, 0.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 1.+0.j - ], - [ - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, - -1.+0.j, 0.+0.j + [ + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 1.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 - 1.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 1.0 + 0.0j, + ], + [ + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + 0.0 + 0.0j, + -1.0 + 0.0j, + 0.0 + 0.0j, + ], ] - ]) - ) - ) + ) + ), + ), ] From a1f1227f645fd2820dc2dcfdf412d753606f374d Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:31:22 +0000 Subject: [PATCH 09/13] - Added maintainer notes. --- .../cirq/contrib/qasm_import/_parser_test.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cirq-core/cirq/contrib/qasm_import/_parser_test.py b/cirq-core/cirq/contrib/qasm_import/_parser_test.py index 8739aa73ca9..42f783f8127 100644 --- a/cirq-core/cirq/contrib/qasm_import/_parser_test.py +++ b/cirq-core/cirq/contrib/qasm_import/_parser_test.py @@ -1050,7 +1050,8 @@ def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int) -> N # Mapping of two-qubit gates and `num_params` two_qubit_param_gates = { - # ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1))): 1, + # TODO: fix and enable commented gates below + # ('cu1', cirq.ControlledGate(QasmUGate(0, 0, 0.1 / np.pi))): 1, # ('cu3', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, # ('cu', cirq.ControlledGate(QasmUGate(0.1 / np.pi, 0.2 / np.pi, 0.3 / np.pi))): 3, ('crx', cirq.ControlledGate(cirq.rx(0.1))): 1, @@ -1109,12 +1110,7 @@ def test_two_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) - def test_two_qubit_param_gates( qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate, num_params: int ) -> None: - if num_params == 1: - params = '(0.1)' - elif num_params == 2: - params = '(0.1, 0.2)' - elif num_params == 3: - params = '(0.1, 0.2, 0.3)' + params = f"({', '.join(f'{0.1 * (x + 1):g}' for x in range(num_params))})" qasm = f""" OPENQASM 2.0; @@ -1313,7 +1309,7 @@ def test_two_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: @pytest.mark.parametrize('qasm_gate,cirq_gate', three_qubit_gates) -def test_three_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) -> None: +def test_three_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: qasm = f""" OPENQASM 2.0; include "qelib1.inc"; @@ -1686,7 +1682,7 @@ def test_three_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: @pytest.mark.parametrize('qasm_gate,cirq_gate', four_qubit_gates) -def test_four_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) -> None: +def test_four_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: qasm = f""" OPENQASM 2.0; include "qelib1.inc"; @@ -1762,7 +1758,7 @@ def test_four_qubit_gates_with_too_much_parameters(qasm_gate: str) -> None: @pytest.mark.parametrize('qasm_gate,cirq_gate', five_qubit_gates) -def test_five_qubit_gates(qasm_gate: str, cirq_gate: cirq.testing.TwoQubitGate) -> None: +def test_five_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate) -> None: qasm = f""" OPENQASM 2.0; include "qelib1.inc"; From a82929fa543e3f5ddc6bde912b743d10a624a61c Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:49:46 +0000 Subject: [PATCH 10/13] - Initial commit for adding MPS synthesis to `cirq.contrib`. --- .../cirq/contrib/mps_synthesis/__init__.py | 17 + .../contrib/mps_synthesis/mps_sequential.py | 323 +++++++++++++++++ .../mps_synthesis/mps_sequential_test.py | 324 ++++++++++++++++++ 3 files changed, 664 insertions(+) create mode 100644 cirq-core/cirq/contrib/mps_synthesis/__init__.py create mode 100644 cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py create mode 100644 cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py diff --git a/cirq-core/cirq/contrib/mps_synthesis/__init__.py b/cirq-core/cirq/contrib/mps_synthesis/__init__.py new file mode 100644 index 00000000000..2075cbb6807 --- /dev/null +++ b/cirq-core/cirq/contrib/mps_synthesis/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2022 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Synthesis for compiling a MPS to a circuit.""" + +from cirq.contrib.mps_synthesis.mps_sequential import Sequential as MPS_StatePrep \ No newline at end of file diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py new file mode 100644 index 00000000000..73ccd5332bd --- /dev/null +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py @@ -0,0 +1,323 @@ +# Copyright 2022 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Synthesis for compiling a MPS to a circuit.""" + +from __future__ import annotations + +import logging +from typing import Literal + +import cirq +import numpy as np +from numpy.typing import NDArray +import quimb.tensor as qtn # type: ignore + +logger = logging.getLogger(__name__) + + +def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: + """Perform Gram-Schmidt orthogonalization on the columns of a matrix + to define the unitary block to encode the MPS. + + Notes + ----- + If a column is (approximately) zero, it is replaced with a random vector. + + Args: + matrix (NDArray[np.complex128]): Input matrix with complex entries. + + Returns: + unitary (NDArray[np.complex128]): A unitary matrix with orthonormal columns + derived from the input matrix. If a column is (approximately) zero, it + is replaced with a random vector. + """ + num_rows, num_columns = matrix.shape + unitary = np.zeros((num_rows, num_columns), dtype=np.complex128) + orthonormal_basis: list[NDArray[np.complex128]] = [] + + for j in range(num_columns): + column = matrix[:, j] + + # If column is (approximately) zero, replace with random + if np.allclose(column, 0): + column = np.random.uniform(-1, 1, num_rows) # type: ignore + if np.iscomplexobj(matrix): + column = column + 1j * np.random.uniform(-1, 1, num_rows) + + # Gram-Schmidt orthogonalization + for basis_vector in orthonormal_basis: + column -= (basis_vector.conj().T @ column) * basis_vector + + # Handle near-zero vectors (linear dependence) + norm = np.linalg.norm(column) + if norm < 1e-12: + is_complex = np.iscomplexobj(matrix) + column = np.random.uniform(-1, 1, num_rows) # type: ignore + if is_complex: + column += 1j * np.random.uniform(-1, 1, num_rows) + for basis_vector in orthonormal_basis: + column -= (basis_vector.conj().T @ column) * basis_vector + + unitary[:, j] = column / np.linalg.norm(column) + orthonormal_basis.append(unitary[:, j]) + + return unitary + + +class Sequential: + def __init__( + self, + max_fidelity_threshold: float = 0.95, + convention: Literal["lsb", "msb"]="lsb" + ) -> None: + """Initialize the Sequential class. + + Args: + max_fidelity_threshold (float): The maximum fidelity required, after + which we can stop the encoding to save depth. Defaults to 0.95. + convention (str): Whether the circuit uses LSB or MSB. By default, + we use "lsb". + """ + self.max_fidelity_threshold = max_fidelity_threshold + self.convention = convention + + def generate_layer( + self, mps: qtn.MatrixProductState + ) -> list[tuple[list[int], NDArray[np.complex128]]]: + """Convert a Matrix Product State (MPS) to a circuit representation + using a single unitary layer. + + Args: + mps (qtn.MatrixProductState): The MPS to convert. + + Returns: + unitary_layer (list[tuple[list[int], NDArray[np.complex128]]]): A list of + tuples representing the unitary layer of the circuit. + Each tuple contains: + - A list of qubit indices (in LSB order) that the unitary acts on. + - A unitary matrix (as a 2D NumPy array) that encodes the MPS. + """ + num_sites = mps.L + + unitary_layer: list[tuple[list[int], NDArray[np.complex128]]] = [] + + for i, tensor in enumerate(reversed(mps.arrays)): + i = num_sites - i - 1 + + # MPS representation uses 1D entanglement, thus we need to define + # the range of the indices via the tensor shape + # i.e., if q0 and q3 are entangled, then regardless of q1 and q2 being + # entangled the entanglement range would be q0-q3 + if i == 0: + d_right, d = tensor.shape + tensor = tensor.reshape((1, d_right, d)) + if i == num_sites - 1: + d_left, d = tensor.shape + tensor = tensor.reshape((d_left, 1, d)) + + tensor = np.swapaxes(tensor, 1, 2) + + # Combine the physical index and right-virtual index of the tensor to construct an isometry + # matrix + d_left, d, d_right = tensor.shape + isometry = tensor.reshape((d * d_left, d_right)) + + qubits = reversed(range(i - int(np.ceil(np.log2(d_left))), i + 1)) + + if self.convention == "lsb": + qubits = [abs(qubit - num_sites + 1) for qubit in qubits] # type: ignore + + # Create all-zero matrix and add the isometry columns + matrix = np.zeros( + (isometry.shape[0], isometry.shape[0]), dtype=isometry.dtype + ) + + # Keep columns for which all ancillas are in the zero state + matrix[:, : isometry.shape[1]] = isometry + + # Perform Gram-Schmidt orthogonalization to ensure the columns are orthonormal + unitary = gram_schmidt(matrix) + + unitary_layer.append((qubits, unitary)) # type: ignore + + return unitary_layer + + def mps_to_circuit_approx( + self, + statevector: NDArray[np.complex128], + max_num_layers: int, + chi_max: int, + ) -> cirq.Circuit: + r"""Approximately encodes the MPS into a circuit via multiple layers + of exact encoding of bond 2 truncated MPS. + + Whilst we can encode the MPS exactly in a single layer, we require + $log(chi) + 1$ qubits for each tensor, which results in larger circuits. + This function uses bond 2 which allows us to encode the MPS using one and + two qubit gates, which results in smaller circuits, and easier to run on + hardware. + + This is the core idea of Ran's paper [1]. + + [1] https://arxiv.org/abs/1908.07958 + + Args: + statevector (NDArray[np.complex128]): The statevector to convert. + max_num_layers (int): The number of layers to use in the circuit. + chi_max (int): The maximum bond dimension of the target MPS. + + Returns: + cirq.Circuit: The generated quantum circuit that encodes the MPS. + """ + mps = qtn.MatrixProductState.from_dense(statevector) + mps: qtn.MatrixProductState = ( + qtn.tensor_1d_compress.tensor_network_1d_compress( + mps, max_bond=chi_max + ) + ) # type: ignore + mps.permute_arrays() + + mps.compress(form="left", max_bond=chi_max) + mps.left_canonicalize(normalize=True) + + compressed_mps = mps.copy(deep=True) + disentangled_mps = mps.copy(deep=True) + + circuit = cirq.Circuit() + qr = cirq.LineQubit.range(mps.L) + + unitary_layers = [] + + # Initialize the zero state |00...0> to serve as comparison + # for the disentangled MPS + zero_state = np.zeros((2**mps.L,), dtype=np.complex128) + zero_state[0] = 1.0 + + # Ran's approach uses a iterative disentanglement of the MPS + # where each layer compresses the MPS to a maximum bond dimension of 2 + # and applies the inverse of the layer to disentangle the MPS + # After a few layers we are adequately close to |00...0> state + # after which we can simply reverse the layers (no inverse) and apply them + # to the |00...0> state to obtain the MPS state + for layer_index in range(max_num_layers): + # Compress the MPS from the previous layer to a maximum bond dimension of 2, + # |ψ_k> -> |ψ'_k> + compressed_mps = disentangled_mps.copy(deep=True) + + # Normalization improves fidelity of the encoding + compressed_mps.normalize() + compressed_mps.compress(form="left", max_bond=2) + + unitary_layer = self.generate_layer(compressed_mps) + unitary_layers.append(unitary_layer) + + # To update the MPS definition, apply the inverse of U_k to disentangle |ψ_k>, + # |ψ_(k+1)> = inv(U_k) @ |ψ_k> + for i, _ in enumerate(unitary_layer): + inverse = unitary_layer[-(i + 1)][1].conj().T + + if inverse.shape[0] == 4: + disentangled_mps.gate_split_(inverse, (i - 1, i)) + else: + disentangled_mps.gate_(inverse, (i), contract=True) + + # Compress the disentangled MPS to a maximum bond dimension of chi_max + # This is to ensure that the disentangled MPS does not grow too large + # and improves the fidelity of the encoding + disentangled_mps: qtn.MatrixProductState = ( + qtn.tensor_1d_compress.tensor_network_1d_compress( # type: ignore + disentangled_mps, max_bond=chi_max + ) + ) + + fidelity = np.abs( + np.vdot(disentangled_mps.to_dense(), zero_state) + ) ** 2 + fidelity = float(np.real(fidelity)) + + if fidelity >= self.max_fidelity_threshold: + logger.info( + f"Reached target fidelity {fidelity}. " + f"{layer_index + 1} layers used." + ) + break + + if layer_index == max_num_layers - 1: + logger.info( + f"Reached fidelity {fidelity} with " + f"maximum number of layers {max_num_layers}." + ) + + # The layers disentangle the MPS to a state close to |00...0> + # inv(U_k) ... inv(U_1) |ψ> = |00...0> + # So, we have to reverse the layers and apply them to the |00...0> state + # to obtain the MPS state + # |ψ> = U_1 ... U_k |00...0> + unitary_layers.reverse() + + for unitary_layer in unitary_layers: + for qubits, unitary in unitary_layer: + qubits = reversed([mps.L - 1 - q for q in qubits]) + + # In certain cases, floating point errors can cause `unitary` + # to not be unitary + # We handle this by using SVD to approximate it again with the + # closest unitary + U, _, Vh = np.linalg.svd(unitary) + unitary = U @ Vh + + circuit.append(cirq.ops.MatrixGate(unitary)(*[qr[q] for q in qubits])) + + return circuit + + def __call__( + self, + statevector: NDArray[np.complex128], + max_num_layers: int = 10 + ) -> cirq.Circuit: + """Call the instance to create the circuit that encodes the statevector. + + Args: + statevector (NDArray[np.complex128]): The statevector to convert. + + Returns: + QuantumCircuit: The generated quantum circuit. + """ + num_qubits = int(np.ceil(np.log2(len(statevector)))) + + # Single qubit statevector is optimal, and cannot be + # further improved given depth of 1 + if num_qubits == 1: + circuit = cirq.Circuit() + q = cirq.LineQubit.range(1) + circuit.append(cirq.StatePreparationChannel(statevector)(q[0])) + return circuit + + circuit = self.mps_to_circuit_approx( + statevector, max_num_layers, 2**num_qubits + ) + + fidelity = np.abs( + np.vdot(cirq.final_state_vector(circuit), statevector) + ) ** 2 + fidelity = float(np.real(fidelity)) + + logger.info( + f"Fidelity: {fidelity:.4f}, " + f"Number of qubits: {num_qubits}, " + f"Number of layers: {max_num_layers}, " + ) + + return circuit \ No newline at end of file diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py new file mode 100644 index 00000000000..60a40bf84c2 --- /dev/null +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py @@ -0,0 +1,324 @@ +# Copyright 2022 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import pytest + +import cirq +from cirq.contrib.mps_synthesis import MPS_StatePrep +import numpy as np + + +@pytest.mark.parametrize("N", [5, 8, 10, 11]) +def test_compile_with_mps_pass(N: int) -> None: + # Generate area-law entangled states for the test + state = np.random.rand(2**N) + 1j * np.random.rand(2**N) + state /= np.linalg.norm(state) + + encoder = MPS_StatePrep() + circuit: cirq.Circuit = encoder(state, max_num_layers=6) + + fidelity = np.vdot( + cirq.final_state_vector(circuit), state + ) + + assert np.abs(fidelity) > 0.85 + + # TODO: Assert circuit depth being lower than exact + +def test_compile_trivial_state_with_mps_pass() -> None: + from cirq.contrib.qasm_import import circuit_from_qasm + + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[10]; + ry(pi/2) q[9]; + rx(pi) q[9]; + rz(pi/4) q[9]; + cx q[9],q[8]; + rz(-pi/4) q[8]; + cx q[9],q[8]; + rz(pi/4) q[8]; + ry(pi/2) q[8]; + rx(pi) q[8]; + rz(pi/4) q[8]; + rz(pi/8) q[9]; + cx q[9],q[7]; + rz(-pi/8) q[7]; + cx q[9],q[7]; + rz(pi/8) q[7]; + cx q[8],q[7]; + rz(-pi/4) q[7]; + cx q[8],q[7]; + rz(pi/4) q[7]; + ry(pi/2) q[7]; + rx(pi) q[7]; + rz(pi/4) q[7]; + rz(pi/8) q[8]; + rz(pi/16) q[9]; + cx q[9],q[6]; + rz(-pi/16) q[6]; + cx q[9],q[6]; + rz(pi/16) q[6]; + cx q[8],q[6]; + rz(-pi/8) q[6]; + cx q[8],q[6]; + rz(pi/8) q[6]; + cx q[7],q[6]; + rz(-pi/4) q[6]; + cx q[7],q[6]; + rz(pi/4) q[6]; + ry(pi/2) q[6]; + rx(pi) q[6]; + rz(pi/4) q[6]; + rz(pi/8) q[7]; + rz(pi/16) q[8]; + rz(pi/32) q[9]; + cx q[9],q[5]; + rz(-pi/32) q[5]; + cx q[9],q[5]; + rz(pi/32) q[5]; + cx q[8],q[5]; + rz(-pi/16) q[5]; + cx q[8],q[5]; + rz(pi/16) q[5]; + cx q[7],q[5]; + rz(-pi/8) q[5]; + cx q[7],q[5]; + rz(pi/8) q[5]; + cx q[6],q[5]; + rz(-pi/4) q[5]; + cx q[6],q[5]; + rz(pi/4) q[5]; + ry(pi/2) q[5]; + rx(pi) q[5]; + rz(pi/4) q[5]; + rz(pi/8) q[6]; + rz(pi/16) q[7]; + rz(pi/32) q[8]; + rz(pi/64) q[9]; + cx q[9],q[4]; + rz(-pi/64) q[4]; + cx q[9],q[4]; + rz(pi/64) q[4]; + cx q[8],q[4]; + rz(-pi/32) q[4]; + cx q[8],q[4]; + rz(pi/32) q[4]; + cx q[7],q[4]; + rz(-pi/16) q[4]; + cx q[7],q[4]; + rz(pi/16) q[4]; + cx q[6],q[4]; + rz(-pi/8) q[4]; + cx q[6],q[4]; + rz(pi/8) q[4]; + cx q[5],q[4]; + rz(-pi/4) q[4]; + cx q[5],q[4]; + rz(pi/4) q[4]; + ry(pi/2) q[4]; + rx(pi) q[4]; + rz(pi/4) q[4]; + rz(pi/8) q[5]; + rz(pi/16) q[6]; + rz(pi/32) q[7]; + rz(pi/64) q[8]; + rz(pi/128) q[9]; + cx q[9],q[3]; + rz(-pi/128) q[3]; + cx q[9],q[3]; + rz(pi/128) q[3]; + cx q[8],q[3]; + rz(-pi/64) q[3]; + cx q[8],q[3]; + rz(pi/64) q[3]; + cx q[7],q[3]; + rz(-pi/32) q[3]; + cx q[7],q[3]; + rz(pi/32) q[3]; + cx q[6],q[3]; + rz(-pi/16) q[3]; + cx q[6],q[3]; + rz(pi/16) q[3]; + cx q[5],q[3]; + rz(-pi/8) q[3]; + cx q[5],q[3]; + rz(pi/8) q[3]; + cx q[4],q[3]; + rz(-pi/4) q[3]; + cx q[4],q[3]; + rz(pi/4) q[3]; + ry(pi/2) q[3]; + rx(pi) q[3]; + rz(pi/4) q[3]; + rz(pi/8) q[4]; + rz(pi/16) q[5]; + rz(pi/32) q[6]; + rz(pi/64) q[7]; + rz(pi/128) q[8]; + rz(pi/256) q[9]; + cx q[9],q[2]; + rz(-pi/256) q[2]; + cx q[9],q[2]; + rz(pi/256) q[2]; + cx q[8],q[2]; + rz(-pi/128) q[2]; + cx q[8],q[2]; + rz(pi/128) q[2]; + cx q[7],q[2]; + rz(-pi/64) q[2]; + cx q[7],q[2]; + rz(pi/64) q[2]; + cx q[6],q[2]; + rz(-pi/32) q[2]; + cx q[6],q[2]; + rz(pi/32) q[2]; + cx q[5],q[2]; + rz(-pi/16) q[2]; + cx q[5],q[2]; + rz(pi/16) q[2]; + cx q[4],q[2]; + rz(-pi/8) q[2]; + cx q[4],q[2]; + rz(pi/8) q[2]; + cx q[3],q[2]; + rz(-pi/4) q[2]; + cx q[3],q[2]; + rz(pi/4) q[2]; + ry(pi/2) q[2]; + rx(pi) q[2]; + rz(pi/4) q[2]; + rz(pi/8) q[3]; + rz(pi/16) q[4]; + rz(pi/32) q[5]; + rz(pi/64) q[6]; + rz(pi/128) q[7]; + rz(pi/256) q[8]; + rz(pi/512) q[9]; + cx q[9],q[1]; + rz(-pi/512) q[1]; + cx q[9],q[1]; + rz(pi/512) q[1]; + cx q[8],q[1]; + rz(-pi/256) q[1]; + cx q[8],q[1]; + rz(pi/256) q[1]; + cx q[7],q[1]; + rz(-pi/128) q[1]; + cx q[7],q[1]; + rz(pi/128) q[1]; + cx q[6],q[1]; + rz(-pi/64) q[1]; + cx q[6],q[1]; + rz(pi/64) q[1]; + cx q[5],q[1]; + rz(-pi/32) q[1]; + cx q[5],q[1]; + rz(pi/32) q[1]; + cx q[4],q[1]; + rz(-pi/16) q[1]; + cx q[4],q[1]; + rz(pi/16) q[1]; + cx q[3],q[1]; + rz(-pi/8) q[1]; + cx q[3],q[1]; + rz(pi/8) q[1]; + cx q[2],q[1]; + rz(-pi/4) q[1]; + cx q[2],q[1]; + rz(pi/4) q[1]; + ry(pi/2) q[1]; + rx(pi) q[1]; + rz(pi/4) q[1]; + rz(pi/8) q[2]; + rz(pi/16) q[3]; + rz(pi/32) q[4]; + rz(pi/64) q[5]; + rz(pi/128) q[6]; + rz(pi/256) q[7]; + rz(pi/512) q[8]; + rz(pi/1024) q[9]; + cx q[9],q[0]; + rz(-pi/1024) q[0]; + cx q[9],q[0]; + rz(pi/1024) q[0]; + cx q[8],q[0]; + rz(-pi/512) q[0]; + cx q[8],q[0]; + rz(pi/512) q[0]; + cx q[7],q[0]; + rz(-pi/256) q[0]; + cx q[7],q[0]; + rz(pi/256) q[0]; + cx q[6],q[0]; + rz(-pi/128) q[0]; + cx q[6],q[0]; + rz(pi/128) q[0]; + cx q[5],q[0]; + rz(-pi/64) q[0]; + cx q[5],q[0]; + rz(pi/64) q[0]; + cx q[4],q[0]; + rz(-pi/32) q[0]; + cx q[4],q[0]; + rz(pi/32) q[0]; + cx q[3],q[0]; + rz(-pi/16) q[0]; + cx q[3],q[0]; + rz(pi/16) q[0]; + cx q[2],q[0]; + rz(-pi/8) q[0]; + cx q[2],q[0]; + rz(pi/8) q[0]; + cx q[1],q[0]; + rz(-pi/4) q[0]; + cx q[1],q[0]; + rz(pi/4) q[0]; + ry(pi/2) q[0]; + rx(pi) q[0]; + cx q[0],q[9]; + cx q[1],q[8]; + cx q[2],q[7]; + cx q[3],q[6]; + cx q[4],q[5]; + cx q[5],q[4]; + cx q[4],q[5]; + cx q[6],q[3]; + cx q[3],q[6]; + cx q[7],q[2]; + cx q[2],q[7]; + cx q[8],q[1]; + cx q[1],q[8]; + cx q[9],q[0]; + cx q[0],q[9]; + """ + + trivial_circuit = circuit_from_qasm(qasm) + + state = cirq.final_state_vector(trivial_circuit) + state /= np.linalg.norm(state) + + encoder = MPS_StatePrep() + circuit: cirq.Circuit = encoder(state, max_num_layers=1) + + fidelity = np.vdot( + cirq.final_state_vector(circuit), state + ) + + assert np.round(np.abs(fidelity), decimals=6) == 1.0 + + # TODO: Assert the circuit has no entangling gates \ No newline at end of file From 76ccfb78c964ab1c14c39d5b2e5d4501d9efb29f Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:57:46 +0000 Subject: [PATCH 11/13] - Added proper single qubit state case. - Fixed failed CIs. --- .../cirq/contrib/mps_synthesis/__init__.py | 2 +- .../contrib/mps_synthesis/mps_sequential.py | 86 +++++++++---------- .../mps_synthesis/mps_sequential_test.py | 30 ++++--- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/cirq-core/cirq/contrib/mps_synthesis/__init__.py b/cirq-core/cirq/contrib/mps_synthesis/__init__.py index 2075cbb6807..b1d0800e894 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/__init__.py +++ b/cirq-core/cirq/contrib/mps_synthesis/__init__.py @@ -14,4 +14,4 @@ """Synthesis for compiling a MPS to a circuit.""" -from cirq.contrib.mps_synthesis.mps_sequential import Sequential as MPS_StatePrep \ No newline at end of file +from cirq.contrib.mps_synthesis.mps_sequential import Sequential as Sequential diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py index 73ccd5332bd..598acd32344 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py @@ -17,12 +17,15 @@ from __future__ import annotations import logging -from typing import Literal +from typing import Literal, TYPE_CHECKING -import cirq import numpy as np -from numpy.typing import NDArray -import quimb.tensor as qtn # type: ignore +import quimb.tensor as qtn + +import cirq + +if TYPE_CHECKING: + from numpy.typing import NDArray logger = logging.getLogger(__name__) @@ -62,7 +65,7 @@ def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: # Handle near-zero vectors (linear dependence) norm = np.linalg.norm(column) - if norm < 1e-12: + if norm < 1e-12: # pragma: no cover is_complex = np.iscomplexobj(matrix) column = np.random.uniform(-1, 1, num_rows) # type: ignore if is_complex: @@ -78,9 +81,7 @@ def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: class Sequential: def __init__( - self, - max_fidelity_threshold: float = 0.95, - convention: Literal["lsb", "msb"]="lsb" + self, max_fidelity_threshold: float = 0.95, convention: Literal["lsb", "msb"] = "lsb" ) -> None: """Initialize the Sequential class. @@ -129,8 +130,8 @@ def generate_layer( tensor = np.swapaxes(tensor, 1, 2) - # Combine the physical index and right-virtual index of the tensor to construct an isometry - # matrix + # Combine the physical index and right-virtual index of the tensor to construct + # an isometry matrix d_left, d, d_right = tensor.shape isometry = tensor.reshape((d * d_left, d_right)) @@ -140,9 +141,7 @@ def generate_layer( qubits = [abs(qubit - num_sites + 1) for qubit in qubits] # type: ignore # Create all-zero matrix and add the isometry columns - matrix = np.zeros( - (isometry.shape[0], isometry.shape[0]), dtype=isometry.dtype - ) + matrix = np.zeros((isometry.shape[0], isometry.shape[0]), dtype=isometry.dtype) # Keep columns for which all ancillas are in the zero state matrix[:, : isometry.shape[1]] = isometry @@ -155,10 +154,7 @@ def generate_layer( return unitary_layer def mps_to_circuit_approx( - self, - statevector: NDArray[np.complex128], - max_num_layers: int, - chi_max: int, + self, statevector: NDArray[np.complex128], max_num_layers: int, chi_max: int ) -> cirq.Circuit: r"""Approximately encodes the MPS into a circuit via multiple layers of exact encoding of bond 2 truncated MPS. @@ -181,12 +177,10 @@ def mps_to_circuit_approx( Returns: cirq.Circuit: The generated quantum circuit that encodes the MPS. """ - mps = qtn.MatrixProductState.from_dense(statevector) - mps: qtn.MatrixProductState = ( - qtn.tensor_1d_compress.tensor_network_1d_compress( - mps, max_bond=chi_max - ) - ) # type: ignore + mps_dense = qtn.MatrixProductState.from_dense(statevector) + mps: qtn.MatrixProductState = qtn.tensor_1d_compress.tensor_network_1d_compress( + mps_dense, max_bond=chi_max + ) mps.permute_arrays() mps.compress(form="left", max_bond=chi_max) @@ -236,28 +230,22 @@ def mps_to_circuit_approx( # Compress the disentangled MPS to a maximum bond dimension of chi_max # This is to ensure that the disentangled MPS does not grow too large # and improves the fidelity of the encoding - disentangled_mps: qtn.MatrixProductState = ( - qtn.tensor_1d_compress.tensor_network_1d_compress( # type: ignore + disentangled_mps: qtn.MatrixProductState = ( # type: ignore + qtn.tensor_1d_compress.tensor_network_1d_compress( disentangled_mps, max_bond=chi_max ) ) - fidelity = np.abs( - np.vdot(disentangled_mps.to_dense(), zero_state) - ) ** 2 + fidelity = np.abs(np.vdot(disentangled_mps.to_dense(), zero_state)) ** 2 fidelity = float(np.real(fidelity)) if fidelity >= self.max_fidelity_threshold: - logger.info( - f"Reached target fidelity {fidelity}. " - f"{layer_index + 1} layers used." - ) + logger.info(f"Reached target fidelity {fidelity}. {layer_index + 1} layers used.") break if layer_index == max_num_layers - 1: logger.info( - f"Reached fidelity {fidelity} with " - f"maximum number of layers {max_num_layers}." + f"Reached fidelity {fidelity} with maximum number of layers {max_num_layers}." ) # The layers disentangle the MPS to a state close to |00...0> @@ -269,7 +257,7 @@ def mps_to_circuit_approx( for unitary_layer in unitary_layers: for qubits, unitary in unitary_layer: - qubits = reversed([mps.L - 1 - q for q in qubits]) + qubits = list(reversed([mps.L - 1 - q for q in qubits])) # In certain cases, floating point errors can cause `unitary` # to not be unitary @@ -283,9 +271,7 @@ def mps_to_circuit_approx( return circuit def __call__( - self, - statevector: NDArray[np.complex128], - max_num_layers: int = 10 + self, statevector: NDArray[np.complex128], max_num_layers: int = 10 ) -> cirq.Circuit: """Call the instance to create the circuit that encodes the statevector. @@ -302,16 +288,24 @@ def __call__( if num_qubits == 1: circuit = cirq.Circuit() q = cirq.LineQubit.range(1) - circuit.append(cirq.StatePreparationChannel(statevector)(q[0])) + + magnitude = abs(statevector) + phase = np.angle(statevector) + + divisor = magnitude[0]**2 + magnitude[1]**2 + alpha_y = 2 * np.arcsin(np.sqrt(magnitude[1]**2 / divisor)) if divisor != 0 else 0.0 + alpha_z = phase[1] - phase[0] + global_phase = sum(phase / len(statevector)) + + circuit.append(cirq.ops.Ry(rads=alpha_y)(q[0])) + circuit.append(cirq.ops.Rz(rads=alpha_z)(q[0])) + circuit.append(cirq.GlobalPhaseGate(np.exp(1j*global_phase))()) + return circuit - circuit = self.mps_to_circuit_approx( - statevector, max_num_layers, 2**num_qubits - ) + circuit = self.mps_to_circuit_approx(statevector, max_num_layers, 2**num_qubits) - fidelity = np.abs( - np.vdot(cirq.final_state_vector(circuit), statevector) - ) ** 2 + fidelity = np.abs(np.vdot(cirq.final_state_vector(circuit), statevector)) ** 2 fidelity = float(np.real(fidelity)) logger.info( @@ -320,4 +314,4 @@ def __call__( f"Number of layers: {max_num_layers}, " ) - return circuit \ No newline at end of file + return circuit diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py index 60a40bf84c2..4650efc97e6 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py @@ -14,12 +14,23 @@ from __future__ import annotations +import numpy as np import pytest import cirq -from cirq.contrib.mps_synthesis import MPS_StatePrep -import numpy as np +from cirq.contrib.mps_synthesis import Sequential + + +def test_compile_single_qubit_state() -> None: + state = np.random.rand(2) + 1j * np.random.rand(2) + state /= np.linalg.norm(state) + encoder = Sequential() + circuit: cirq.Circuit = encoder(state, max_num_layers=1) + + fidelity = np.vdot(cirq.final_state_vector(circuit), state) + + assert np.round(np.abs(fidelity), decimals=6) == 1.0 @pytest.mark.parametrize("N", [5, 8, 10, 11]) def test_compile_with_mps_pass(N: int) -> None: @@ -27,17 +38,16 @@ def test_compile_with_mps_pass(N: int) -> None: state = np.random.rand(2**N) + 1j * np.random.rand(2**N) state /= np.linalg.norm(state) - encoder = MPS_StatePrep() + encoder = Sequential() circuit: cirq.Circuit = encoder(state, max_num_layers=6) - fidelity = np.vdot( - cirq.final_state_vector(circuit), state - ) + fidelity = np.vdot(cirq.final_state_vector(circuit), state) assert np.abs(fidelity) > 0.85 # TODO: Assert circuit depth being lower than exact + def test_compile_trivial_state_with_mps_pass() -> None: from cirq.contrib.qasm_import import circuit_from_qasm @@ -312,13 +322,11 @@ def test_compile_trivial_state_with_mps_pass() -> None: state = cirq.final_state_vector(trivial_circuit) state /= np.linalg.norm(state) - encoder = MPS_StatePrep() + encoder = Sequential() circuit: cirq.Circuit = encoder(state, max_num_layers=1) - fidelity = np.vdot( - cirq.final_state_vector(circuit), state - ) + fidelity = np.vdot(cirq.final_state_vector(circuit), state) assert np.round(np.abs(fidelity), decimals=6) == 1.0 - # TODO: Assert the circuit has no entangling gates \ No newline at end of file + # TODO: Assert the circuit has no entangling gates From 211e1eb86c0140f23fb306a09aa8ce986f878f0c Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Sun, 16 Nov 2025 07:35:08 +0000 Subject: [PATCH 12/13] - Fixed format issues. --- cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py | 10 +++++----- .../cirq/contrib/mps_synthesis/mps_sequential_test.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py index 598acd32344..aecaee7ddb8 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py @@ -65,7 +65,7 @@ def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: # Handle near-zero vectors (linear dependence) norm = np.linalg.norm(column) - if norm < 1e-12: # pragma: no cover + if norm < 1e-12: # pragma: no cover is_complex = np.iscomplexobj(matrix) column = np.random.uniform(-1, 1, num_rows) # type: ignore if is_complex: @@ -230,7 +230,7 @@ def mps_to_circuit_approx( # Compress the disentangled MPS to a maximum bond dimension of chi_max # This is to ensure that the disentangled MPS does not grow too large # and improves the fidelity of the encoding - disentangled_mps: qtn.MatrixProductState = ( # type: ignore + disentangled_mps: qtn.MatrixProductState = ( # type: ignore qtn.tensor_1d_compress.tensor_network_1d_compress( disentangled_mps, max_bond=chi_max ) @@ -292,14 +292,14 @@ def __call__( magnitude = abs(statevector) phase = np.angle(statevector) - divisor = magnitude[0]**2 + magnitude[1]**2 - alpha_y = 2 * np.arcsin(np.sqrt(magnitude[1]**2 / divisor)) if divisor != 0 else 0.0 + divisor = magnitude[0] ** 2 + magnitude[1] ** 2 + alpha_y = 2 * np.arcsin(np.sqrt(magnitude[1] ** 2 / divisor)) if divisor != 0 else 0.0 alpha_z = phase[1] - phase[0] global_phase = sum(phase / len(statevector)) circuit.append(cirq.ops.Ry(rads=alpha_y)(q[0])) circuit.append(cirq.ops.Rz(rads=alpha_z)(q[0])) - circuit.append(cirq.GlobalPhaseGate(np.exp(1j*global_phase))()) + circuit.append(cirq.GlobalPhaseGate(np.exp(1j * global_phase))()) return circuit diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py index 4650efc97e6..dd364e8da71 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py @@ -32,6 +32,7 @@ def test_compile_single_qubit_state() -> None: assert np.round(np.abs(fidelity), decimals=6) == 1.0 + @pytest.mark.parametrize("N", [5, 8, 10, 11]) def test_compile_with_mps_pass(N: int) -> None: # Generate area-law entangled states for the test From e3cc16df5dd20069609fc34ff4ed8aa8f15e4cbb Mon Sep 17 00:00:00 2001 From: "A.C.E07" <73689800+ACE07-Sev@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:09:18 +0000 Subject: [PATCH 13/13] - Changed `gram_schmidt` to `_gram_schmidt` to address the function as internal module only. - Added assertion to `_gram_schmidt` to raise an error should `matrix` not be square to catch edge cases not known at the time of changing this code. - Changed class name to `MPSSequential`. - Added class docstring. - Removed `convention` from class attributes. - Added indentation to docstring sections where needed. - Removed `logger` uses. - Added `mps_circuit_from_statevector` to `__init__.py` for ease of use. - Used `cirq.testing.random_superposition` in testers where possible, except `test_compile_area_law_states` which requires strictly area-law entangled states as part of the test. - Rewrote `test_compile_trivial_state_with_mps_pass` to avoid using qasm and avoid inter-module dependency. - Used `np.testing.assert_allclose` for about exact matching assertions. --- .../cirq/contrib/mps_synthesis/__init__.py | 29 +- .../contrib/mps_synthesis/mps_sequential.py | 108 +++--- .../mps_synthesis/mps_sequential_test.py | 327 ++---------------- 3 files changed, 119 insertions(+), 345 deletions(-) diff --git a/cirq-core/cirq/contrib/mps_synthesis/__init__.py b/cirq-core/cirq/contrib/mps_synthesis/__init__.py index b1d0800e894..d8ebe15170d 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/__init__.py +++ b/cirq-core/cirq/contrib/mps_synthesis/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022 The Cirq Developers +# Copyright 2025 The Cirq Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,4 +14,29 @@ """Synthesis for compiling a MPS to a circuit.""" -from cirq.contrib.mps_synthesis.mps_sequential import Sequential as Sequential +from __future__ import annotations +from typing import TYPE_CHECKING + +from cirq.contrib.mps_synthesis.mps_sequential import MPSSequential + +if TYPE_CHECKING: + import numpy as np + from numpy.typing import NDArray + import cirq + + +def mps_circuit_from_statevector( + statevector: NDArray[np.complex128], max_num_layers: int = 10, target_fidelity: float = 0.95 +) -> cirq.Circuit: + """Create the circuit that encodes the statevector using MPS synthesis. + + Args: + statevector: The target statevector to be encoded. + max_num_layers: The maximum number of layers allowed in the circuit. + target_fidelity: The target fidelity for the approximation. + + Returns: + A cirq.Circuit that encodes the statevector. + """ + encoder = MPSSequential(max_fidelity_threshold=target_fidelity) + return encoder(statevector, max_num_layers=max_num_layers) diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py index aecaee7ddb8..09df1004a1d 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential.py @@ -1,4 +1,4 @@ -# Copyright 2022 The Cirq Developers +# Copyright 2025 The Cirq Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ from __future__ import annotations -import logging -from typing import Literal, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np import quimb.tensor as qtn @@ -27,30 +26,44 @@ if TYPE_CHECKING: from numpy.typing import NDArray -logger = logging.getLogger(__name__) - -def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: - """Perform Gram-Schmidt orthogonalization on the columns of a matrix +def _gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: + """Perform Gram-Schmidt orthogonalization on the columns of a square matrix to define the unitary block to encode the MPS. Notes ----- - If a column is (approximately) zero, it is replaced with a random vector. + This implementation provides slightly better fidelity compared to the Schmidt + decomposition approach used in scipy.linalg.qr for our use case. If you find + better performance with other implementations, please file an issue at + https://github.com/quantumlib/Cirq/issues + + accompanied by the proposed code and benchmark results. Args: - matrix (NDArray[np.complex128]): Input matrix with complex entries. + matrix (NDArray[np.complex128]): Square matrix with complex entries. Returns: unitary (NDArray[np.complex128]): A unitary matrix with orthonormal columns derived from the input matrix. If a column is (approximately) zero, it is replaced with a random vector. + + Raises: + ValueError: If the input matrix is not square. """ - num_rows, num_columns = matrix.shape - unitary = np.zeros((num_rows, num_columns), dtype=np.complex128) + if matrix.shape[0] != matrix.shape[1]: + raise ValueError( + "Input matrix is expected to be square. " + f"Got shape {matrix.shape} instead. " + "If this is not an error, please file an issue at " + "https://github.com/quantumlib/Cirq/issues" + ) + + num_rows = matrix.shape[0] + unitary = np.zeros((num_rows, num_rows), dtype=np.complex128) orthonormal_basis: list[NDArray[np.complex128]] = [] - for j in range(num_columns): + for j in range(num_rows): column = matrix[:, j] # If column is (approximately) zero, replace with random @@ -64,12 +77,11 @@ def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: column -= (basis_vector.conj().T @ column) * basis_vector # Handle near-zero vectors (linear dependence) - norm = np.linalg.norm(column) - if norm < 1e-12: # pragma: no cover - is_complex = np.iscomplexobj(matrix) + if np.linalg.norm(column) < 1e-12: # pragma: no cover column = np.random.uniform(-1, 1, num_rows) # type: ignore - if is_complex: - column += 1j * np.random.uniform(-1, 1, num_rows) + if np.iscomplexobj(matrix): + column = column + 1j * np.random.uniform(-1, 1, num_rows) + for basis_vector in orthonormal_basis: column -= (basis_vector.conj().T @ column) * basis_vector @@ -79,20 +91,35 @@ def gram_schmidt(matrix: NDArray[np.complex128]) -> NDArray[np.complex128]: return unitary -class Sequential: - def __init__( - self, max_fidelity_threshold: float = 0.95, convention: Literal["lsb", "msb"] = "lsb" - ) -> None: +class MPSSequential: + """MPS to Circuit synthesis via sequential layers of unitaries. + + This class provides methods to convert a Matrix Product State (MPS) + into a quantum circuit representation using layers of two-qubit and single-qubit unitaries + in the form of a staircase. + + This approach allows for approximately encoding an area-law entangled state + using O(N) circuit depth, where N is the number of qubits by leveraging the MPS structure + as an intermediate representation. This is an exponential improvement over the + standard method of encoding arbitrary states, which typically requires O(2^N) depth. + + The analytical decomposition is based on the approach presented in: + [arXiv:1908.07958](https://arxiv.org/abs/1908.07958){:.external} + + Further improvements for future include using sweeping techniques from tensor network + literature to iteratively improve the fidelity of the encoding with a fixed number of + layers. The algorithm is described in detail in the following paper: + [arXiv:2209.00595](https://arxiv.org/abs/2209.00595){:.external} + """ + + def __init__(self, max_fidelity_threshold: float = 0.95) -> None: """Initialize the Sequential class. Args: - max_fidelity_threshold (float): The maximum fidelity required, after - which we can stop the encoding to save depth. Defaults to 0.95. - convention (str): Whether the circuit uses LSB or MSB. By default, - we use "lsb". + max_fidelity_threshold: The maximum fidelity required, after + which we can stop the encoding to save depth. Defaults to 0.95. """ self.max_fidelity_threshold = max_fidelity_threshold - self.convention = convention def generate_layer( self, mps: qtn.MatrixProductState @@ -105,7 +132,7 @@ def generate_layer( Returns: unitary_layer (list[tuple[list[int], NDArray[np.complex128]]]): A list of - tuples representing the unitary layer of the circuit. + tuples representing the unitary layer of the circuit. Each tuple contains: - A list of qubit indices (in LSB order) that the unitary acts on. - A unitary matrix (as a 2D NumPy array) that encodes the MPS. @@ -135,21 +162,19 @@ def generate_layer( d_left, d, d_right = tensor.shape isometry = tensor.reshape((d * d_left, d_right)) - qubits = reversed(range(i - int(np.ceil(np.log2(d_left))), i + 1)) - - if self.convention == "lsb": - qubits = [abs(qubit - num_sites + 1) for qubit in qubits] # type: ignore + qubits = [ + num_sites - 1 - q for q in range(i, i - int(np.ceil(np.log2(d_left))) - 1, -1) + ] - # Create all-zero matrix and add the isometry columns matrix = np.zeros((isometry.shape[0], isometry.shape[0]), dtype=isometry.dtype) # Keep columns for which all ancillas are in the zero state matrix[:, : isometry.shape[1]] = isometry # Perform Gram-Schmidt orthogonalization to ensure the columns are orthonormal - unitary = gram_schmidt(matrix) + unitary = _gram_schmidt(matrix) - unitary_layer.append((qubits, unitary)) # type: ignore + unitary_layer.append((qubits, unitary)) return unitary_layer @@ -240,14 +265,8 @@ def mps_to_circuit_approx( fidelity = float(np.real(fidelity)) if fidelity >= self.max_fidelity_threshold: - logger.info(f"Reached target fidelity {fidelity}. {layer_index + 1} layers used.") break - if layer_index == max_num_layers - 1: - logger.info( - f"Reached fidelity {fidelity} with maximum number of layers {max_num_layers}." - ) - # The layers disentangle the MPS to a state close to |00...0> # inv(U_k) ... inv(U_1) |ψ> = |00...0> # So, we have to reverse the layers and apply them to the |00...0> state @@ -305,13 +324,4 @@ def __call__( circuit = self.mps_to_circuit_approx(statevector, max_num_layers, 2**num_qubits) - fidelity = np.abs(np.vdot(cirq.final_state_vector(circuit), statevector)) ** 2 - fidelity = float(np.real(fidelity)) - - logger.info( - f"Fidelity: {fidelity:.4f}, " - f"Number of qubits: {num_qubits}, " - f"Number of layers: {max_num_layers}, " - ) - return circuit diff --git a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py index dd364e8da71..8eaef697d6d 100644 --- a/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py +++ b/cirq-core/cirq/contrib/mps_synthesis/mps_sequential_test.py @@ -1,4 +1,4 @@ -# Copyright 2022 The Cirq Developers +# Copyright 2025 The Cirq Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,316 +18,55 @@ import pytest import cirq -from cirq.contrib.mps_synthesis import Sequential +from cirq.contrib.mps_synthesis import mps_circuit_from_statevector +from cirq.testing import random_superposition def test_compile_single_qubit_state() -> None: - state = np.random.rand(2) + 1j * np.random.rand(2) - state /= np.linalg.norm(state) - - encoder = Sequential() - circuit: cirq.Circuit = encoder(state, max_num_layers=1) - - fidelity = np.vdot(cirq.final_state_vector(circuit), state) - - assert np.round(np.abs(fidelity), decimals=6) == 1.0 + state = random_superposition(2) + circuit: cirq.Circuit = mps_circuit_from_statevector(state, max_num_layers=1) + np.testing.assert_allclose(cirq.final_state_vector(circuit), state, atol=1e-6) @pytest.mark.parametrize("N", [5, 8, 10, 11]) -def test_compile_with_mps_pass(N: int) -> None: - # Generate area-law entangled states for the test +def test_compile_area_law_states(N: int) -> None: + # Given `random_superposition` can generate volume-law entangled states, + # we manually construct an exclusively area-law entangled state here state = np.random.rand(2**N) + 1j * np.random.rand(2**N) state /= np.linalg.norm(state) - encoder = Sequential() - circuit: cirq.Circuit = encoder(state, max_num_layers=6) - + circuit: cirq.Circuit = mps_circuit_from_statevector(state, max_num_layers=6) fidelity = np.vdot(cirq.final_state_vector(circuit), state) - assert np.abs(fidelity) > 0.85 # TODO: Assert circuit depth being lower than exact def test_compile_trivial_state_with_mps_pass() -> None: - from cirq.contrib.qasm_import import circuit_from_qasm - - qasm = """ - OPENQASM 2.0; - include "qelib1.inc"; - qreg q[10]; - ry(pi/2) q[9]; - rx(pi) q[9]; - rz(pi/4) q[9]; - cx q[9],q[8]; - rz(-pi/4) q[8]; - cx q[9],q[8]; - rz(pi/4) q[8]; - ry(pi/2) q[8]; - rx(pi) q[8]; - rz(pi/4) q[8]; - rz(pi/8) q[9]; - cx q[9],q[7]; - rz(-pi/8) q[7]; - cx q[9],q[7]; - rz(pi/8) q[7]; - cx q[8],q[7]; - rz(-pi/4) q[7]; - cx q[8],q[7]; - rz(pi/4) q[7]; - ry(pi/2) q[7]; - rx(pi) q[7]; - rz(pi/4) q[7]; - rz(pi/8) q[8]; - rz(pi/16) q[9]; - cx q[9],q[6]; - rz(-pi/16) q[6]; - cx q[9],q[6]; - rz(pi/16) q[6]; - cx q[8],q[6]; - rz(-pi/8) q[6]; - cx q[8],q[6]; - rz(pi/8) q[6]; - cx q[7],q[6]; - rz(-pi/4) q[6]; - cx q[7],q[6]; - rz(pi/4) q[6]; - ry(pi/2) q[6]; - rx(pi) q[6]; - rz(pi/4) q[6]; - rz(pi/8) q[7]; - rz(pi/16) q[8]; - rz(pi/32) q[9]; - cx q[9],q[5]; - rz(-pi/32) q[5]; - cx q[9],q[5]; - rz(pi/32) q[5]; - cx q[8],q[5]; - rz(-pi/16) q[5]; - cx q[8],q[5]; - rz(pi/16) q[5]; - cx q[7],q[5]; - rz(-pi/8) q[5]; - cx q[7],q[5]; - rz(pi/8) q[5]; - cx q[6],q[5]; - rz(-pi/4) q[5]; - cx q[6],q[5]; - rz(pi/4) q[5]; - ry(pi/2) q[5]; - rx(pi) q[5]; - rz(pi/4) q[5]; - rz(pi/8) q[6]; - rz(pi/16) q[7]; - rz(pi/32) q[8]; - rz(pi/64) q[9]; - cx q[9],q[4]; - rz(-pi/64) q[4]; - cx q[9],q[4]; - rz(pi/64) q[4]; - cx q[8],q[4]; - rz(-pi/32) q[4]; - cx q[8],q[4]; - rz(pi/32) q[4]; - cx q[7],q[4]; - rz(-pi/16) q[4]; - cx q[7],q[4]; - rz(pi/16) q[4]; - cx q[6],q[4]; - rz(-pi/8) q[4]; - cx q[6],q[4]; - rz(pi/8) q[4]; - cx q[5],q[4]; - rz(-pi/4) q[4]; - cx q[5],q[4]; - rz(pi/4) q[4]; - ry(pi/2) q[4]; - rx(pi) q[4]; - rz(pi/4) q[4]; - rz(pi/8) q[5]; - rz(pi/16) q[6]; - rz(pi/32) q[7]; - rz(pi/64) q[8]; - rz(pi/128) q[9]; - cx q[9],q[3]; - rz(-pi/128) q[3]; - cx q[9],q[3]; - rz(pi/128) q[3]; - cx q[8],q[3]; - rz(-pi/64) q[3]; - cx q[8],q[3]; - rz(pi/64) q[3]; - cx q[7],q[3]; - rz(-pi/32) q[3]; - cx q[7],q[3]; - rz(pi/32) q[3]; - cx q[6],q[3]; - rz(-pi/16) q[3]; - cx q[6],q[3]; - rz(pi/16) q[3]; - cx q[5],q[3]; - rz(-pi/8) q[3]; - cx q[5],q[3]; - rz(pi/8) q[3]; - cx q[4],q[3]; - rz(-pi/4) q[3]; - cx q[4],q[3]; - rz(pi/4) q[3]; - ry(pi/2) q[3]; - rx(pi) q[3]; - rz(pi/4) q[3]; - rz(pi/8) q[4]; - rz(pi/16) q[5]; - rz(pi/32) q[6]; - rz(pi/64) q[7]; - rz(pi/128) q[8]; - rz(pi/256) q[9]; - cx q[9],q[2]; - rz(-pi/256) q[2]; - cx q[9],q[2]; - rz(pi/256) q[2]; - cx q[8],q[2]; - rz(-pi/128) q[2]; - cx q[8],q[2]; - rz(pi/128) q[2]; - cx q[7],q[2]; - rz(-pi/64) q[2]; - cx q[7],q[2]; - rz(pi/64) q[2]; - cx q[6],q[2]; - rz(-pi/32) q[2]; - cx q[6],q[2]; - rz(pi/32) q[2]; - cx q[5],q[2]; - rz(-pi/16) q[2]; - cx q[5],q[2]; - rz(pi/16) q[2]; - cx q[4],q[2]; - rz(-pi/8) q[2]; - cx q[4],q[2]; - rz(pi/8) q[2]; - cx q[3],q[2]; - rz(-pi/4) q[2]; - cx q[3],q[2]; - rz(pi/4) q[2]; - ry(pi/2) q[2]; - rx(pi) q[2]; - rz(pi/4) q[2]; - rz(pi/8) q[3]; - rz(pi/16) q[4]; - rz(pi/32) q[5]; - rz(pi/64) q[6]; - rz(pi/128) q[7]; - rz(pi/256) q[8]; - rz(pi/512) q[9]; - cx q[9],q[1]; - rz(-pi/512) q[1]; - cx q[9],q[1]; - rz(pi/512) q[1]; - cx q[8],q[1]; - rz(-pi/256) q[1]; - cx q[8],q[1]; - rz(pi/256) q[1]; - cx q[7],q[1]; - rz(-pi/128) q[1]; - cx q[7],q[1]; - rz(pi/128) q[1]; - cx q[6],q[1]; - rz(-pi/64) q[1]; - cx q[6],q[1]; - rz(pi/64) q[1]; - cx q[5],q[1]; - rz(-pi/32) q[1]; - cx q[5],q[1]; - rz(pi/32) q[1]; - cx q[4],q[1]; - rz(-pi/16) q[1]; - cx q[4],q[1]; - rz(pi/16) q[1]; - cx q[3],q[1]; - rz(-pi/8) q[1]; - cx q[3],q[1]; - rz(pi/8) q[1]; - cx q[2],q[1]; - rz(-pi/4) q[1]; - cx q[2],q[1]; - rz(pi/4) q[1]; - ry(pi/2) q[1]; - rx(pi) q[1]; - rz(pi/4) q[1]; - rz(pi/8) q[2]; - rz(pi/16) q[3]; - rz(pi/32) q[4]; - rz(pi/64) q[5]; - rz(pi/128) q[6]; - rz(pi/256) q[7]; - rz(pi/512) q[8]; - rz(pi/1024) q[9]; - cx q[9],q[0]; - rz(-pi/1024) q[0]; - cx q[9],q[0]; - rz(pi/1024) q[0]; - cx q[8],q[0]; - rz(-pi/512) q[0]; - cx q[8],q[0]; - rz(pi/512) q[0]; - cx q[7],q[0]; - rz(-pi/256) q[0]; - cx q[7],q[0]; - rz(pi/256) q[0]; - cx q[6],q[0]; - rz(-pi/128) q[0]; - cx q[6],q[0]; - rz(pi/128) q[0]; - cx q[5],q[0]; - rz(-pi/64) q[0]; - cx q[5],q[0]; - rz(pi/64) q[0]; - cx q[4],q[0]; - rz(-pi/32) q[0]; - cx q[4],q[0]; - rz(pi/32) q[0]; - cx q[3],q[0]; - rz(-pi/16) q[0]; - cx q[3],q[0]; - rz(pi/16) q[0]; - cx q[2],q[0]; - rz(-pi/8) q[0]; - cx q[2],q[0]; - rz(pi/8) q[0]; - cx q[1],q[0]; - rz(-pi/4) q[0]; - cx q[1],q[0]; - rz(pi/4) q[0]; - ry(pi/2) q[0]; - rx(pi) q[0]; - cx q[0],q[9]; - cx q[1],q[8]; - cx q[2],q[7]; - cx q[3],q[6]; - cx q[4],q[5]; - cx q[5],q[4]; - cx q[4],q[5]; - cx q[6],q[3]; - cx q[3],q[6]; - cx q[7],q[2]; - cx q[2],q[7]; - cx q[8],q[1]; - cx q[1],q[8]; - cx q[9],q[0]; - cx q[0],q[9]; - """ - - trivial_circuit = circuit_from_qasm(qasm) + # Define a circuit that produces a trivial state + # aka a product state + trivial_circuit = cirq.Circuit() + qubits = cirq.LineQubit.range(10) + trivial_circuit.append(cirq.ry(np.pi / 2).on(qubits[9])) + trivial_circuit.append(cirq.rx(np.pi).on(qubits[9])) + trivial_circuit.append(cirq.rz(np.pi / 4).on(qubits[9])) + for i in range(8, -1, -1): + trivial_circuit.append(cirq.CNOT(qubits[i + 1], qubits[i])) + trivial_circuit.append(cirq.rz(-np.pi / (2 ** (9 - i))).on(qubits[i])) + trivial_circuit.append(cirq.CNOT(qubits[i + 1], qubits[i])) + trivial_circuit.append(cirq.rz(np.pi / (2 ** (9 - i))).on(qubits[i])) + trivial_circuit.append(cirq.ry(np.pi / 2).on(qubits[i])) + trivial_circuit.append(cirq.rx(np.pi).on(qubits[i])) + trivial_circuit.append(cirq.rz(np.pi / 4).on(qubits[i])) + for i in range(8, -1, -1): + trivial_circuit.append(cirq.rz(np.pi / (2 ** (9 - i))).on(qubits[i])) + for i in range(8, -1, -1): + trivial_circuit.append(cirq.CNOT(qubits[i + 1], qubits[i])) + for i in range(8, -1, -1): + trivial_circuit.append(cirq.CNOT(qubits[i], qubits[i + 1])) state = cirq.final_state_vector(trivial_circuit) - state /= np.linalg.norm(state) - - encoder = Sequential() - circuit: cirq.Circuit = encoder(state, max_num_layers=1) - - fidelity = np.vdot(cirq.final_state_vector(circuit), state) - - assert np.round(np.abs(fidelity), decimals=6) == 1.0 + circuit: cirq.Circuit = mps_circuit_from_statevector(state, max_num_layers=1) + np.testing.assert_allclose(cirq.final_state_vector(circuit), state, atol=1e-6) # TODO: Assert the circuit has no entangling gates