From f6cdee708a7e158ac725eca971528c4f5ea7678c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Fri, 6 Nov 2020 17:18:30 +0100 Subject: [PATCH 01/42] Add U2 and U3 gates with definitions identical to IBM QisKit --- projectq/ops/_basics.py | 147 +++++++++++++++++++++++++++++++++-- projectq/ops/_basics_test.py | 96 ++++++++++++++++++++++- projectq/ops/_gates.py | 23 ++++++ projectq/ops/_gates_test.py | 22 ++++++ 4 files changed, 279 insertions(+), 9 deletions(-) diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 85469658..a3fa174b 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -63,6 +63,22 @@ class NotInvertible(Exception): """ +def _round_angle(angle, mod_pi): + rounded_angle = round(float(angle) % (mod_pi * math.pi), ANGLE_PRECISION) + if rounded_angle > mod_pi * math.pi - ANGLE_TOLERANCE: + rounded_angle = 0. + return rounded_angle + + +def _angle_to_str(angle, symbols): + if symbols: + return ('{}{}'.format(round(angle / math.pi, 3), + unicodedata.lookup("GREEK SMALL LETTER PI"))) + else: + return "{}".format(angle) + + + class BasicGate: """Base class of all gates. (Don't use it directly but derive from it).""" @@ -335,10 +351,7 @@ def __init__(self, angle): angle (float): Angle of rotation (saved modulo 4 * pi) """ super().__init__() - rounded_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) - if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: - rounded_angle = 0.0 - self.angle = rounded_angle + self.angle = _round_angle(angle, 4) def __str__(self): """ @@ -418,6 +431,127 @@ def is_identity(self): return self.angle in (0.0, 4 * math.pi) +class U3Gate(BasicGate): + """ + Base class of for a general unitary single-qubit gate. + + All three angles are continuous parameters. The inverse is the same gate + with the negated argument. Rotation gates of the same class can be merged + by adding the angles. The continuous parameter are modulo 4 * pi. + """ + def __init__(self, theta, phi, lamda): + """ + Initialize a general unitary single-qubit gate. + + Args: + theta (float): Angle of rotation (saved modulo 4 * pi) + phi (float): Angle of rotation (saved modulo 4 * pi) + lamda (float): Angle of rotation (saved modulo 4 * pi) + """ + BasicGate.__init__(self) + self.theta = _round_angle(theta, 4) + self.phi = _round_angle(phi, 4) + self.lamda = _round_angle(lamda, 4) + + def __str__(self): + """ + Return the string representation of a U3Gate. + + Returns the class name and the angle as + + .. code-block:: python + + [CLASSNAME]([ANGLE]) + """ + return self.to_string() + + def to_string(self, symbols=False): + """ + Return the string representation of a U3Gate. + + Args: + symbols (bool): uses the pi character and round the angle for a + more user friendly display if True, full angle + written in radian otherwise. + """ + return (str(self.__class__.__name__) + '({},{},{})'.format( + _angle_to_str(self.theta, symbols), + _angle_to_str(self.phi, symbols), + _angle_to_str(self.lamda, symbols))) + + def tex_str(self): + """ + Return the Latex string representation of a BasicRotationGate. + + Returns the class name and the angle as a subscript, i.e. + + .. code-block:: latex + + [CLASSNAME]$_[ANGLE]$ + """ + return str(self.__class__.__name__) + "$({}\\pi,{}\\pi,{}\\pi)$".format( + round(self.theta / math.pi, 3), + round(self.phi / math.pi, 3), + round(self.lamda / math.pi, 3)) + + def get_inverse(self): + """ + Return the inverse of this rotation gate (negate the angle, return new + object). + """ + if (self.theta, self.phi, self.lamda) == (0, 0, 0): + return self.__class__(0, 0, 0) + else: + return self.__class__(-self.theta + 4 * math.pi, + -self.phi + 4 * math.pi, + -self.lamda + 4 * math.pi) + + def get_merged(self, other): + """ + Return self merged with another gate. + + Default implementation handles rotation gate of the same type, where + angles are simply added. + + Args: + other: Rotation gate of same type. + + Raises: + NotMergeable: For non-rotation gates or rotation gates of + different type. + + Returns: + New object representing the merged gates. + """ + if isinstance(other, self.__class__): + return self.__class__(self.theta + other.theta, + self.phi + other.phi, + self.lamda + other.lamda) + raise NotMergeable("Can't merge different types of rotation gates.") + + def __eq__(self, other): + """ Return True if same class and same rotation angle. """ + if isinstance(other, self.__class__): + return ((self.theta, self.phi, self.lamda) + == (other.theta, other.phi, other.lamda)) + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) + + def is_identity(self): + """ + Return True if the gate is equivalent to an Identity gate + """ + return ((self.theta == 0. or self.theta == 4 * math.pi) + and (self.phi == 0. or self.phi == 4 * math.pi) + and (self.lamda == 0. or self.lamda == 4 * math.pi)) + + class BasicPhaseGate(BasicGate): """ Base class for all phase gates. @@ -435,10 +569,7 @@ def __init__(self, angle): angle (float): Angle of rotation (saved modulo 2 * pi) """ super().__init__() - rounded_angle = round(float(angle) % (2.0 * math.pi), ANGLE_PRECISION) - if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: - rounded_angle = 0.0 - self.angle = rounded_angle + self.angle = _round_angle(angle, 2) def __str__(self): """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 35b19986..75bda79f 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -212,7 +212,7 @@ def test_basic_rotation_gate_is_identity(): assert basic_rotation_gate5.is_identity() -def test_basic_rotation_gate_comparison_and_hash(): +def test_u3_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) basic_rotation_gate2 = _basics.BasicRotationGate(0.5) basic_rotation_gate3 = _basics.BasicRotationGate(0.5 + 4 * math.pi) @@ -234,6 +234,100 @@ def test_basic_rotation_gate_comparison_and_hash(): assert not basic_gate == basic_rotation_gate6 assert basic_rotation_gate2 != _basics.BasicRotationGate(0.5 + 2 * math.pi) +@pytest.mark.parametrize("input_angle, modulo_angle", + [(2.0, 2.0), (17., 4.4336293856408275), + (-0.5 * math.pi, 3.5 * math.pi), (4 * math.pi, 0)]) +def test_u3_gate_init(input_angle, modulo_angle): + # Test internal representation + gate = _basics.U3Gate(input_angle, input_angle, input_angle) + assert gate.theta == pytest.approx(modulo_angle) + assert gate.phi == pytest.approx(modulo_angle) + assert gate.lamda == pytest.approx(modulo_angle) + + +def test_u3_gate_str(): + gate = _basics.U3Gate(math.pi, math.pi, math.pi) + assert (str(gate) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)") + assert gate.to_string(symbols=True) == u"U3Gate(1.0π,1.0π,1.0π)" + assert (gate.to_string(symbols=False) == + "U3Gate(3.14159265359,3.14159265359,3.14159265359)") + + +def test_u3_tex_str(): + gate = _basics.U3Gate(0.5 * math.pi, 0.5 * math.pi, 0.5 * math.pi) + assert gate.tex_str() == "U3Gate$(0.5\\pi,0.5\\pi,0.5\\pi)$" + gate = _basics.U3Gate(4 * math.pi - 1e-13, + 4 * math.pi - 1e-13, + 4 * math.pi - 1e-13) + assert gate.tex_str() == "U3Gate$(0.0\\pi,0.0\\pi,0.0\\pi)$" + + +@pytest.mark.parametrize("input_angle, inverse_angle", + [(2.0, -2.0 + 4 * math.pi), (-0.5, 0.5), (0.0, 0)]) +def test_u3_gate_get_inverse(input_angle, inverse_angle): + u3_gate = _basics.U3Gate(input_angle, input_angle, input_angle) + inverse = u3_gate.get_inverse() + assert isinstance(inverse, _basics.U3Gate) + assert inverse.theta == pytest.approx(inverse_angle) + assert inverse.phi == pytest.approx(inverse_angle) + assert inverse.lamda == pytest.approx(inverse_angle) + + +def test_u3_gate_get_merged(): + basic_gate = _basics.BasicGate() + u3_gate1 = _basics.U3Gate(0.5, 0.5, 0.5) + u3_gate2 = _basics.U3Gate(1.0, 1.0, 1.0) + u3_gate3 = _basics.U3Gate(1.5, 1.5, 1.5) + with pytest.raises(_basics.NotMergeable): + u3_gate1.get_merged(basic_gate) + merged_gate = u3_gate1.get_merged(u3_gate2) + assert merged_gate == u3_gate3 + + +def test_u3_gate_is_identity(): + u3_gate1 = _basics.U3Gate(0., 0., 0.) + u3_gate2 = _basics.U3Gate( + 1. * math.pi, 1. * math.pi, 1. * math.pi) + u3_gate3 = _basics.U3Gate( + 2. * math.pi, 2. * math.pi, 2. * math.pi) + u3_gate4 = _basics.U3Gate( + 3. * math.pi, 3. * math.pi, 3. * math.pi) + u3_gate5 = _basics.U3Gate( + 4. * math.pi, 4. * math.pi, 4. * math.pi) + assert u3_gate1.is_identity() + assert not u3_gate2.is_identity() + assert not u3_gate3.is_identity() + assert not u3_gate4.is_identity() + assert u3_gate5.is_identity() + + +def test_u3_gate_comparison_and_hash(): + u3gate1 = _basics.U3Gate(0.5, 0.5, 0.5) + u3gate2 = _basics.U3Gate(0.5, 0.5, 0.5) + u3gate3 = _basics.U3Gate( + 0.5 + 4 * math.pi, 0.5 + 4 * math.pi, 0.5 + 4 * math.pi) + assert u3gate1 == u3gate2 + assert hash(u3gate1) == hash(u3gate2) + assert u3gate1 == u3gate3 + assert hash(u3gate1) == hash(u3gate3) + u3gate4 = _basics.U3Gate(0.50000001, 0.50000001, 0.50000001) + # Test __ne__: + assert u3gate4 != u3gate1 + # Test one gate close to 4*pi the other one close to 0 + u3gate5 = _basics.U3Gate(1.e-13, 1.e-13, 1.e-13) + u3gate6 = _basics.U3Gate(4 * math.pi - 1.e-13, + 4 * math.pi - 1.e-13, + 4 * math.pi - 1.e-13) + assert u3gate5 == u3gate6 + assert u3gate6 == u3gate5 + assert hash(u3gate5) == hash(u3gate6) + # Test different types of gates + basic_gate = _basics.BasicGate() + assert not basic_gate == u3gate6 + assert u3gate2 != _basics.U3Gate(0.5 + 2 * math.pi, + 0.5 + 2 * math.pi, + 0.5 + 2 * math.pi) + @pytest.mark.parametrize( "input_angle, modulo_angle", diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 10527a86..876ee7b5 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -54,6 +54,7 @@ ClassicalInstructionGate, FastForwardingGate, SelfInverseGate, + U3Gate, ) from ._command import apply_command from ._metagates import get_inverse @@ -309,6 +310,28 @@ def matrix(self): ) +class U3(U3Gate): + @property + def matrix(self): + return np.matrix( + [ + [ + cmath.exp(-0.5j * (self.phi + self.lamda)) + math.cos(0.5 * self.theta), + -cmath.exp(-0.5j * (self.phi - self.lamda)) + math.sin(0.5 * self.theta), + ], + [ + cmath.exp(0.5j * (self.phi - self.lamda)) + math.sin(0.5 * self.theta), + cmath.exp(0.5j * (self.phi + self.lamda)) + math.cos(0.5 * self.theta), + ], + ] + ) + + +class U2(U3): + def __init__(self, phi, lamda): + super().__init__(math.pi / 2, phi, lamda) + + class Rxx(BasicRotationGate): """RotationXX gate class.""" diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index ab431b92..cf58d5f9 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -150,6 +150,28 @@ def test_rz(angle): assert np.allclose(gate.matrix, expected_matrix) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_u3(angle): + gate = _gates.U3(angle, angle, angle) + expected_matrix = np.matrix([[cmath.exp(-1j * angle) + math.cos(.5 * angle), + -1 + math.sin(.5 * angle)], + [1 + math.sin(.5 * angle), + cmath.exp(1j * angle) + math.cos(.5 * angle)]]) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_u2(angle): + gate = _gates.U2(angle, angle) + expected_matrix = np.matrix([[cmath.exp(-1j * angle) + math.cos(.25 * math.pi), + -1 + math.sin(.25 * math.pi)], + [1 + math.sin(.25 * math.pi), + cmath.exp(1j * angle) + math.cos(.25 * math.pi)]]) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rxx(angle): gate = _gates.Rxx(angle) From 2980b292dd640dd1b99f74a07e29085b5e72b785 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Fri, 6 Nov 2020 18:11:11 +0100 Subject: [PATCH 02/42] Add ProjectQ -> OpenQASM conversion --- projectq/backends/_qasm.py | 310 +++++++++++++++++++++++++++++++ projectq/backends/_qasm_test.py | 317 ++++++++++++++++++++++++++++++++ 2 files changed, 627 insertions(+) create mode 100644 projectq/backends/_qasm.py create mode 100644 projectq/backends/_qasm_test.py diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py new file mode 100644 index 00000000..73c3abf8 --- /dev/null +++ b/projectq/backends/_qasm.py @@ -0,0 +1,310 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +""" Backend to convert ProjectQ commands to OpenQASM. """ + +from copy import deepcopy + +from projectq.cengines import BasicEngine +from projectq.meta import get_control_count +from projectq.ops import (X, NOT, Y, Z, T, Tdag, S, Sdag, H, Ph, R, Rx, Ry, Rz, + Swap, Measure, Allocate, Deallocate, Barrier, + FlushGate) + +# ============================================================================== + + +class OpenQASMBackend(BasicEngine): + """ + Engine to convert ProjectQ commands to OpenQASM format (either string or + file) + """ + def __init__(self, + collate=True, + collate_callback=None, + qubit_callback=lambda qubit_id: 'q{}'.format(qubit_id), + bit_callback=lambda qubit_id: 'c{}'.format(qubit_id), + qubit_id_mapping_redux=True): + """ + Initialize an OpenQASMBackend object. + + Contrary to OpenQASM, ProjectQ does not impose the restriction that a + programm must start with qubit/bit allocations and end with some + measurements. + + The user can configure what happens each time a FlushGate() is + encountered by setting the `collate` and `collate_func` arguments to + an OpenQASMBackend constructor, + + Args: + output (list,file): + collate (bool): If True, simply append commands to the exisiting + file/string list when a FlushGate is received. If False, you + need to specify `collate_callback` arguments as well. + collate (function): Only has an effect if `collate` is False. Each + time a FlushGate is received, this callback function will be + called. + Function signature: Callable[[Sequence[str]], None] + qubit_callback (function): Callback function called upon create of + each qubit to generate a name for the qubit. + Function signature: Callable[[int], str] + bit_callback (function): Callback function called upon create of + each qubit to generate a name for the qubit. + Function signature: Callable[[int], str] + qubit_id_mapping_redux (bool): If True, try to allocate new Qubit + IDs to the next available qreg/creg (if any), otherwise create + a new qreg/creg. If False, simply create a new qreg/creg for + each new Qubit ID + """ + super().__init__() + self._collate = collate + self._collate_callback = None if collate else collate_callback + self._gen_qubit_name = qubit_callback + self._gen_bit_name = bit_callback + self._qubit_id_mapping_redux = qubit_id_mapping_redux + + self._output = [] + self._qreg_dict = dict() + self._creg_dict = dict() + self._reg_index = 0 + self._available_indices = [] + + self._insert_openqasm_header() + + @property + def qasm(self): + return self._output + + def is_available(self, cmd): + """ + Return true if the command can be executed. + + Args: + cmd (Command): Command for which to check availability + """ + gate = cmd.gate + n_controls = get_control_count(cmd) + + is_available = False + + if gate in (Measure, Allocate, Deallocate, Barrier): + is_available = True + + if n_controls == 0: + if gate in (H, S, Sdag, T, Tdag, X, NOT, Y, Z, Swap): + is_available = True + if isinstance(gate, (Ph, R, Rx, Ry, Rz)): + is_available = True + elif n_controls == 1: + if gate in (H, X, NOT, Y, Z): + is_available = True + if isinstance(gate, ( + R, + Rz, + )): + is_available = True + elif n_controls == 2: + if gate in (X, NOT): + is_available = True + + if not is_available: + return False + if not self.is_last_engine: + return self.next_engine.is_available(cmd) + else: + return True + + def receive(self, command_list): + """ + Receives a command list and, for each command, stores it until + completion. + + Args: + command_list: List of commands to execute + """ + for cmd in command_list: + if not cmd.gate == FlushGate(): + self._store(cmd) + else: + self._reset_after_flush() + + if not self.is_last_engine: + self.send(command_list) + + def _store(self, cmd): + """ + Temporarily store the command cmd. + + Translates the command and stores it the _openqasm_circuit attribute + (self._openqasm_circuit) + + Args: + cmd: Command to store + """ + gate = cmd.gate + n_controls = get_control_count(cmd) + + def _format_angle(angle): + return '({})'.format(angle) + + _ccontrolled_gates_func = { + X: 'ccx', + NOT: 'ccx', + } + _controlled_gates_func = { + H: 'ch', + Ph: 'cu1', + R: 'cu1', + Rz: 'crz', + X: 'cx', + NOT: 'cx', + Y: 'cy', + Z: 'cz', + Swap: 'cswap' + } + _gates_func = { + Barrier: 'barrier', + H: 'h', + Ph: 'u1', + S: 's', + Sdag: 'sdg', + T: 't', + Tdag: 'tdg', + R: 'u1', + Rx: 'rx', + Ry: 'ry', + Rz: 'rz', + X: 'x', + NOT: 'x', + Y: 'y', + Z: 'z', + Swap: 'swap' + } + + if gate == Allocate: + add = True + + # Perform qubit index reduction if possible. This typically means + # that existing qubit keep their indices between FlushGates but + # that qubit indices of deallocated qubit may be reused. + if self._qubit_id_mapping_redux and self._available_indices: + add = False + index = self._available_indices.pop() + else: + index = self._reg_index + self._reg_index += 1 + + qb_id = cmd.qubits[0][0].id + + # TODO: only create bit for qubits that are actually measured + self._qreg_dict[qb_id] = self._gen_qubit_name(index) + self._creg_dict[qb_id] = self._gen_bit_name(index) + + if add: + self._output.append('qubit {};'.format(self._qreg_dict[qb_id])) + self._output.append('bit {};'.format(self._creg_dict[qb_id])) + + elif gate == Deallocate: + qb_id = cmd.qubits[0][0].id + + if self._qubit_id_mapping_redux: + self._available_indices.append(qb_id) + del self._qreg_dict[qb_id] + del self._creg_dict[qb_id] + + elif gate == Measure: + assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 + qb_id = cmd.qubits[0][0].id + + self._output.append('{} = measure {};'.format( + self._creg_dict[qb_id], self._qreg_dict[qb_id])) + + elif n_controls == 2: + targets = [ + self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg + ] + controls = [self._qreg_dict[qb.id] for qb in cmd.control_qubits] + + try: + self._output.append('{} {};'.format( + _ccontrolled_gates_func[gate], + ','.join(controls + targets))) + except KeyError: + raise RuntimeError( + 'Unable to perform {} gate with n=2 control qubits'.format( + gate)) + + elif n_controls == 1: + target_qureg = [ + self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg + ] + + try: + if isinstance(gate, Ph): + self._output.append('{}{} {},{};'.format( + _controlled_gates_func[type(gate)], + _format_angle(-gate.angle / 2.), + self._qreg_dict[cmd.control_qubits[0].id], + target_qureg[0])) + elif isinstance(gate, ( + R, + Rz, + )): + self._output.append('{}{} {},{};'.format( + _controlled_gates_func[type(gate)], + _format_angle(gate.angle), + self._qreg_dict[cmd.control_qubits[0].id], + target_qureg[0])) + else: + self._output.append('{} {},{};'.format( + _controlled_gates_func[gate], + self._qreg_dict[cmd.control_qubits[0].id], + *target_qureg)) + except KeyError: + raise RuntimeError( + 'Unable to perform {} gate with n=1 control qubits'.format( + gate)) + else: + target_qureg = [ + self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg + ] + if isinstance(gate, Ph): + self._output.append('{}{} {};'.format( + _gates_func[type(gate)], _format_angle(-gate.angle / 2.), + target_qureg[0])) + elif isinstance(gate, (R, Rx, Ry, Rz)): + self._output.append('{}{} {};'.format(_gates_func[type(gate)], + _format_angle(gate.angle), + target_qureg[0])) + else: + self._output.append('{} {};'.format(_gates_func[gate], + *target_qureg)) + + def _insert_openqasm_header(self): + self._output.append('OPENQASM 3;') + self._output.append('include "stdgates.inc";') + + def _reset_after_flush(self): + """ + Reset the internal quantum circuit after a FlushGate + """ + if self._collate: + self._output.append('# ' + '=' * 80) + else: + self._collate_callback(deepcopy(self._output)) + self._output.clear() + self._insert_openqasm_header() + for qubit_name in self._qreg_dict.values(): + self._output.append('qubit {};'.format(qubit_name)) + for bit_name in self._creg_dict.values(): + self._output.append('bit {};'.format(bit_name)) diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py new file mode 100644 index 00000000..190b607d --- /dev/null +++ b/projectq/backends/_qasm_test.py @@ -0,0 +1,317 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +"""Tests for projectq.cengines._openqasm.py.""" + +import pytest + +import re +from projectq.cengines import MainEngine, DummyEngine +from projectq.meta import Control +from projectq.ops import (X, NOT, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, + Ry, Rz, Swap, Allocate, Deallocate, Measure, Barrier, + Entangle, Command, All) +from ._qasm import OpenQASMBackend + +# ============================================================================== + + +def test_qasm_init(): + eng = OpenQASMBackend() + assert isinstance(eng.qasm, list) + assert not eng._qreg_dict + assert not eng._creg_dict + assert eng._reg_index == 0 + assert not eng._available_indices + + +@pytest.mark.parametrize("qubit_id_redux", [False, True]) +def test_qasm_allocate_deallocate(qubit_id_redux): + backend = OpenQASMBackend(qubit_id_mapping_redux=qubit_id_redux) + assert backend._qubit_id_mapping_redux == qubit_id_redux + + eng = MainEngine(backend) + qubit = eng.allocate_qubit() + eng.flush() + + assert len(backend._qreg_dict) == 1 + assert len(backend._creg_dict) == 1 + assert backend._reg_index == 1 + assert not backend._available_indices + qasm = '\n'.join(eng.backend.qasm) + assert re.search(r'qubit\s+q0', qasm) + assert re.search(r'bit\s+c0', qasm) + + qureg = eng.allocate_qureg(5) + eng.flush() + + assert len(backend._qreg_dict) == 6 + assert len(backend._creg_dict) == 6 + assert backend._reg_index == 6 + assert not backend._available_indices + qasm = '\n'.join(eng.backend.qasm) + for i in range(1, 6): + assert re.search(r'qubit\s+q{}'.format(i), qasm) + assert re.search(r'bit\s+c{}'.format(i), qasm) + + del qubit + eng.flush() + if qubit_id_redux: + assert len(backend._qreg_dict) == 5 + assert len(backend._creg_dict) == 5 + assert backend._reg_index == 6 + assert backend._available_indices == [0] + else: + assert len(backend._qreg_dict) == 6 + assert len(backend._creg_dict) == 6 + assert backend._reg_index == 6 + assert not backend._available_indices + + qubit = eng.allocate_qubit() + eng.flush() + + if qubit_id_redux: + assert len(backend._qreg_dict) == 6 + assert len(backend._creg_dict) == 6 + assert backend._reg_index == 6 + assert not backend._available_indices + else: + assert len(backend._qreg_dict) == 7 + assert len(backend._creg_dict) == 7 + assert backend._reg_index == 7 + assert not backend._available_indices + + +@pytest.mark.parametrize("gate, is_available", + [(X, True), (Y, True), (Z, True), (T, True), + (Tdagger, True), (S, True), (Sdagger, True), + (Allocate, True), (Deallocate, True), (Measure, True), + (NOT, True), (Rx(0.5), True), (Ry(0.5), True), + (Rz(0.5), True), (R(0.5), True), (Ph(0.5), True), + (Barrier, True), (Entangle, False)], + ids=lambda l: str(l)) +def test_qasm_is_available(gate, is_available): + eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) + qubit1 = eng.allocate_qubit() + cmd = Command(eng, gate, (qubit1, )) + eng.is_available(cmd) == is_available + + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) + qubit1 = eng.allocate_qubit() + cmd = Command(eng, gate, (qubit1, )) + eng.is_available(cmd) == is_available + + +@pytest.mark.parametrize("gate, is_available", + [(H, True), (X, True), (NOT, True), (Y, True), + (Z, True), (Rz(0.5), True), (R(0.5), True), + (Rx(0.5), False), (Ry(0.5), False)], + ids=lambda l: str(l)) +def test_qasm_is_available_1control(gate, is_available): + eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + cmd = Command(eng, gate, (qubit1, ), controls=qureg) + assert eng.is_available(cmd) == is_available + + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + cmd = Command(eng, gate, (qubit1, ), controls=qureg) + assert eng.is_available(cmd) == is_available + + +@pytest.mark.parametrize("gate, is_available", + [(X, True), (NOT, True), (Y, False), (Z, False), + (Rz(0.5), False), (R(0.5), False), (Rx(0.5), False), + (Ry(0.5), False)], + ids=lambda l: str(l)) +def test_qasm_is_available_2control(gate, is_available): + eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(2) + cmd = Command(eng, gate, (qubit1, ), controls=qureg) + assert eng.is_available(cmd) == is_available + + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(2) + cmd = Command(eng, gate, (qubit1, ), controls=qureg) + assert eng.is_available(cmd) == is_available + + +def test_qasm_test_qasm_single_qubit_gates(): + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) + qubit = eng.allocate_qubit() + + H | qubit + S | qubit + T | qubit + Sdagger | qubit + Tdagger | qubit + X | qubit + Y | qubit + Z | qubit + R(0.5) | qubit + Rx(0.5) | qubit + Ry(0.5) | qubit + Rz(0.5) | qubit + Ph(0.5) | qubit + NOT | qubit + Measure | qubit + eng.flush() + + qasm = eng.backend.qasm + # Note: ignoring header and footer for comparison + assert qasm[2:-1] == [ + 'qubit q0;', 'bit c0;', 'h q0;', 's q0;', 't q0;', 'sdg q0;', 'tdg q0;', + 'x q0;', 'y q0;', 'z q0;', 'u1(0.5) q0;', 'rx(0.5) q0;', 'ry(0.5) q0;', + 'rz(0.5) q0;', 'u1(-0.25) q0;', 'x q0;', 'c0 = measure q0;' + ] + + +def test_qasm_test_qasm_single_qubit_gates_control(): + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) + qubit = eng.allocate_qubit() + ctrl = eng.allocate_qubit() + + with Control(eng, ctrl): + H | qubit + X | qubit + Y | qubit + Z | qubit + NOT | qubit + R(0.5) | qubit + Rz(0.5) | qubit + Ph(0.5) | qubit + All(Measure) | qubit + ctrl + eng.flush() + + qasm = eng.backend.qasm + # Note: ignoring header and footer for comparison + assert qasm[2:-1] == [ + 'qubit q0;', 'bit c0;', 'qubit q1;', 'bit c1;', 'ch q1,q0;', + 'cx q1,q0;', 'cy q1,q0;', 'cz q1,q0;', 'cx q1,q0;', 'cu1(0.5) q1,q0;', + 'crz(0.5) q1,q0;', 'cu1(-0.25) q1,q0;', 'c0 = measure q0;', + 'c1 = measure q1;' + ] + + # Also test invalid gates with 1 control qubits + with pytest.raises(RuntimeError): + with Control(eng, ctrl): + T | qubit + eng.flush() + + +def test_qasm_test_qasm_single_qubit_gates_controls(): + eng = MainEngine(backend=OpenQASMBackend(), engine_list=[], verbose=True) + qubit = eng.allocate_qubit() + ctrls = eng.allocate_qureg(2) + + with Control(eng, ctrls): + X | qubit + NOT | qubit + eng.flush() + + qasm = eng.backend.qasm + # Note: ignoring header and footer for comparison + assert qasm[2:-1] == [ + 'qubit q0;', + 'bit c0;', + 'qubit q1;', + 'bit c1;', + 'qubit q2;', + 'bit c2;', + 'ccx q1,q2,q0;', + 'ccx q1,q2,q0;', + ] + + # Also test invalid gates with 2 control qubits + with pytest.raises(RuntimeError): + with Control(eng, ctrls): + Y | qubit + eng.flush() + + +def test_qasm_no_collate(): + qasm_list = [] + + def _process(output): + qasm_list.append(output) + + eng = MainEngine(backend=OpenQASMBackend(collate=False, + collate_callback=_process, + qubit_id_mapping_redux=False), + engine_list=[]) + qubit = eng.allocate_qubit() + ctrls = eng.allocate_qureg(2) + + H | qubit + with Control(eng, ctrls): + X | qubit + NOT | qubit + + eng.flush() + + All(Measure) | qubit + ctrls + eng.flush() + + print(qasm_list) + assert len(qasm_list) == 2 + + # Note: ignoring header for comparison + assert qasm_list[0][2:] == [ + 'qubit q0;', + 'bit c0;', + 'qubit q1;', + 'bit c1;', + 'qubit q2;', + 'bit c2;', + 'h q0;', + 'ccx q1,q2,q0;', + 'ccx q1,q2,q0;', + ] + + # Note: ignoring header for comparison + assert qasm_list[1][2:] == [ + 'qubit q0;', + 'qubit q1;', + 'qubit q2;', + 'bit c0;', + 'bit c1;', + 'bit c2;', + 'c0 = measure q0;', + 'c1 = measure q1;', + 'c2 = measure q2;', + ] + + +def test_qasm_name_callback(): + def _qubit(index): + return 'qubit_{}'.format(index) + + def _bit(index): + return 'classical_bit_{}'.format(index) + + eng = MainEngine(backend=OpenQASMBackend(qubit_callback=_qubit, + bit_callback=_bit), + engine_list=[]) + + qubit = eng.allocate_qubit() + Measure | qubit + + qasm = eng.backend.qasm + assert qasm[2:] == [ + 'qubit qubit_0;', 'bit classical_bit_0;', + 'classical_bit_0 = measure qubit_0;' + ] From 5a4d1a9b2302f5931a8a809c6b67b7b6895e7659 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Fri, 6 Nov 2020 18:09:46 +0100 Subject: [PATCH 03/42] Add qiskit -> ProjectQ parser using qiskit --- projectq/libs/qasm/__init__.py | 37 ++ projectq/libs/qasm/_parse_qasm_qiskit.py | 173 +++++++++ projectq/libs/qasm/_parse_qasm_qiskit_test.py | 355 ++++++++++++++++++ projectq/libs/qasm/_qiskit_conv.py | 39 ++ 4 files changed, 604 insertions(+) create mode 100644 projectq/libs/qasm/__init__.py create mode 100644 projectq/libs/qasm/_parse_qasm_qiskit.py create mode 100644 projectq/libs/qasm/_parse_qasm_qiskit_test.py create mode 100644 projectq/libs/qasm/_qiskit_conv.py diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py new file mode 100644 index 00000000..f39cabd7 --- /dev/null +++ b/projectq/libs/qasm/__init__.py @@ -0,0 +1,37 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +""" +Contains functions/classes to handle OpenQASM +""" + +try: + from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str +except ImportError: # pragma: no cover + import warnings + + err = ('Unable to import qiskit\n' + 'Please install it (e.g. using the command: ' + 'python -m pip install qiskit') + + warnings.warn(err + 'c\n' + 'The provided read_qasm_* functions will systematically' + 'raise a RuntimeError') + + def read_qasm_file(eng, filename): + # pylint: disable=unused-argument + raise RuntimeError(err) + + def read_qasm_str(eng, qasm_str): + # pylint: disable=unused-argument + raise RuntimeError(err) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py new file mode 100644 index 00000000..3ca2f35f --- /dev/null +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -0,0 +1,173 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +""" Define function to read OpenQASM file format (using Qiskit). """ + +from projectq.ops import All, Measure, ControlledGate, SwapGate + +from qiskit.circuit import QuantumCircuit + +from ._qiskit_conv import gates_conv_table + +# ============================================================================== + + +def apply_gate(gate, qubits): + """ + Apply a gate to some qubits while separating control and target qubits. + + + Args: + gate (BasicGate): Instance of a ProjectQ gate + qubits (list): List of ProjectQ qubits the gate applies to. + """ + if isinstance(gate, ControlledGate): + ctrls = qubits[:gate._n] + qubits = qubits[gate._n:] + if isinstance(gate._gate, SwapGate): + assert len(qubits) == 2 + gate | (ctrls, qubits[0], qubits[1]) + else: + gate | (ctrls, qubits) + elif isinstance(gate, SwapGate): + assert len(qubits) == 2 + gate | (qubits[0], qubits[1]) + else: + gate | qubits + + +def apply_op(eng, gate, qubits, bits, bits_map): + """ + Apply a qiskit operation. + + This function takes care of converting between qiskit gates and ProjectQ + gates, as well as handling the translation between qiskit's and ProjectQ's + qubit and bits. + + Args: + eng (MainEngine): MainEngine to use to the operation(s) + gate (qiskit.Gate): Qiskit gate to apply + qubits (list): List of ProjectQ qubits to apply the gate to + bits (list): List of classical bits to apply the gate to + """ + + if bits: + # Only measurement gates have clasical bits + assert len(qubits) == len(bits) + All(Measure) | qubits + eng.flush() + + for idx, bit in enumerate(bits): + bits_map[bit.register.name][idx] = bool(qubits[idx]) + else: + if gate.name not in gates_conv_table: + if not gate._definition: + return + + gate_args = {gate._definition.qregs[0].name: qubits} + + for gate_sub, quregs_sub, bits_sub in gate._definition.data: + # OpenQASM 2.0 limitation... + assert gate.name != 'measure' and not bits_sub + apply_op(eng, gate_sub, [ + gate_args[qubit.register.name][qubit.index] + for qubit in quregs_sub + ], [], bits_map) + else: + if gate.params: + gate_projectq = gates_conv_table[gate.name](*gate.params) + else: + gate_projectq = gates_conv_table[gate.name] + + if gate.condition: + # OpenQASM 2.0 + cbit, value = gate.condition + + # Note: apparently, it is illegal to write if (c0[0] == 1) + # so we always assume that index == 0... + if bits_map[cbit.name][0] == value: + apply_gate(gate_projectq, qubits) + else: + apply_gate(gate_projectq, qubits) + + +def _convert_qiskit_circuit(eng, qc): + """ + Convert a QisKit circuit and convert it to ProjectQ commands. + + This function supports OpenQASM 2.0 (3.0 is experimental) + + Args: + eng (MainEngine): MainEngine to use for creating qubits and commands. + qc (qiskit.QuantumCircuit): Quantum circuit to process + + Note: + At this time, we support most of OpenQASM 2.0 and some of 3.0, + although the latter is still experimental. + """ + # Create maps between qiskit and ProjectQ for qubits and bits + qubits_map = { + qureg.name: eng.allocate_qureg(qureg.size) + for qureg in qc.qregs + } + bits_map = {bit.name: [None] * bit.size for bit in qc.cregs} + + # Convert all the gates to ProjectQ commands + for gate, quregs, bits in qc.data: + apply_op( + eng, gate, + [qubits_map[qubit.register.name][qubit.index] + for qubit in quregs], bits, bits_map) + + return qubits_map, bits_map + +def read_qasm_str(eng, qasm_str): + """ + Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to + ProjectQ commands. + + This version of the function uses Qiskit in order to parse the *.qasm + file. + + Args: + eng (MainEngine): MainEngine to use for creating qubits and commands. + filename (string): Path to *.qasm file + + Note: + At this time, we support most of OpenQASM 2.0 and some of 3.0, + although the latter is still experimental. + """ + qc = QuantumCircuit.from_qasm_str(qasm_str) + return _convert_qiskit_circuit(eng, qc) + + +def read_qasm_file(eng, filename): + """ + Read an OpenQASM (2.0, 3.0 is experimental) file and convert it to + ProjectQ commands. + + This version of the function uses Qiskit in order to parse the *.qasm + file. + + Args: + eng (MainEngine): MainEngine to use for creating qubits and commands. + filename (string): Path to *.qasm file + + Note: + At this time, we support most of OpenQASM 2.0 and some of 3.0, + although the latter is still experimental. + """ + + qc = QuantumCircuit.from_qasm_file(filename) + + return _convert_qiskit_circuit(eng, qc) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py new file mode 100644 index 00000000..f7540270 --- /dev/null +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -0,0 +1,355 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +"""Tests for projectq.libs.qasm._parse_qasm_qiskit.py.""" + +import pytest + +from projectq.types import WeakQubitRef +from projectq.cengines import MainEngine, DummyEngine, BasicEngine +from projectq.ops import (X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, + U2, U3, Swap, Toffoli, Barrier, All, C, Allocate, + Deallocate, Measure, FlushGate) +from copy import deepcopy +import tempfile + +# ============================================================================== + +try: + from ._parse_qasm_qiskit import (apply_gate, apply_op, read_qasm_file, + read_qasm_str) + no_qiskit = False +except ImportError: + no_qiskit = True + +# ============================================================================== + + +class TestEngine(DummyEngine): + def __init__(self, measurement_result=True): + super().__init__(save_commands=True) + self.is_last_engine = True + self.measurement_result = measurement_result + + def is_available(self, cmd): + return True + + def receive(self, command_list): + for cmd in command_list: + if cmd.gate == Measure: + for qureg in cmd.qubits: + for qubit in qureg: + self.main_engine.set_measurement_result( + qubit, self.measurement_result) + else: + self.received_commands.append(cmd) + + +# ============================================================================== + + +@pytest.fixture +def eng(): + # rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) + + return MainEngine(backend=TestEngine(True), engine_list=[]) + + +# ============================================================================== + + +@pytest.mark.skipif(no_qiskit, reason="Could not import Qiskit") +@pytest.mark.parametrize( + 'gate, n_qubits', (list( + map(lambda x: + (x, 1), [ + X, Y, Z, S, Sdagger, T, Tdagger, H, Barrier, + Ph(1.12), + Rx(1.12), + Ry(1.12), + Rz(1.12), + R(1.12) + ])) + list(map(lambda x: + (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) + + list(map(lambda x: + (x, 3), [Toffoli, C(Swap), Barrier])) + + list(map(lambda x: (x, 10), [Barrier]))), + ids=lambda x: str(x)) +def test_apply_gate(gate, n_qubits): + backend = DummyEngine() + backend.is_last_engine = True + + gate.engine = backend + qubits = [WeakQubitRef(backend, idx) for idx in range(n_qubits)] + + apply_gate(gate, qubits) + + +# ============================================================================== +# OpenQASM 2.0 tests + + +def test_read_qasm2_empty(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +""" + + read_qasm_str(eng, qasm_str) + assert not eng.backend.received_commands + + +def test_read_qasm2_allocation(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[1]; +""" + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert not bits_map + assert len(engine.backend.received_commands) == 1 + assert engine.backend.received_commands[0].gate == Allocate + + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[4]; +""" + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert not bits_map + assert len(engine.backend.received_commands) == 4 + assert all(cmd.gate == Allocate for cmd in engine.backend.received_commands) + + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[3]; +creg c[1]; +creg d[2]; +""" + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert bits_map == {'c': [None], 'd': [None, None]} + assert len(engine.backend.received_commands) == 3 + assert all(cmd.gate == Allocate for cmd in engine.backend.received_commands) + + +@pytest.mark.parametrize('gate, projectq_gate', + [('x', X), ('y', Y), ('z', Z), ('h', H), ('s', S), + ('sdg', Sdagger), ('t', T), ('tdg', Tdagger), + ('rx(1.12)', Rx(1.12)), ('ry(1.12)', Ry(1.12)), + ('rz(1.12)', Rz(1.12)), ('u1(1.12)', Rz(1.12)), + ('u2(1.12,1.12)', U2(1.12, 1.12)), + ('u3(1.12,1.12,1.12)', U3(1.12, 1.12, 1.12))], + ids=lambda x: str(x)) +def test_read_qasm2_single_qubit_gates(eng, gate, projectq_gate): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[1]; +{} q[0]; +""".format(gate) + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert not bits_map + assert len(engine.backend.received_commands) == 2 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == projectq_gate + + +@pytest.mark.parametrize( + 'gate, projectq_gate', + [ + ('cx', X), + ('cy', Y), + ('cz', Z), + ('ch', H), + ('crz(1.12)', Rz(1.12)), + ('cu1(1.12)', Rz(1.12)), + # ('cu2(1.12,1.12)', U2(1.12, 1.12)), + ('cu3(1.12,1.12,1.12)', U3(1.12, 1.12, 1.12)), + ('swap', Swap) + ], + ids=lambda x: str(x)) +def test_read_qasm2_two_qubit_gates(eng, gate, projectq_gate): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +{} q[0], q[1]; +""".format(gate) + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert not bits_map + assert len(engine.backend.received_commands) == 3 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == Allocate + cmd = engine.backend.received_commands[2] + assert cmd.gate == projectq_gate + if cmd.gate == Swap: + assert not cmd.control_qubits + assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 2 + else: + assert len(cmd.control_qubits) == 1 + assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 + + +@pytest.mark.parametrize('gate, projectq_gate', [('ccx', X), ('cswap', Swap)], + ids=lambda x: str(x)) +def test_read_qasm2_three_qubit_gates(eng, gate, projectq_gate): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[3]; +{} q[0], q[1], q[2]; +""".format(gate) + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert not bits_map + assert len(engine.backend.received_commands) == 4 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == Allocate + assert engine.backend.received_commands[2].gate == Allocate + cmd = engine.backend.received_commands[3] + assert cmd.gate == projectq_gate + if cmd.gate == Swap: + assert len(cmd.control_qubits) == 1 + assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 2 + else: + assert len(cmd.control_qubits) == 2 + assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 + + +def test_read_qasm2_if_expr(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c0[1]; +measure q[0] -> c0; +if (c0 == 1) x q[0]; +""" + + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert bits_map == {'c0': [True]} + assert len(engine.backend.received_commands) == 4 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == Allocate + assert engine.backend.received_commands[2].gate == FlushGate() + cmd = engine.backend.received_commands[3] + assert cmd.gate == X + assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 + + engine = deepcopy(eng) + engine.backend.measurement_result = False + qubits_map, bits_map = read_qasm_str(engine, qasm_str) + assert bits_map == {'c0': [False]} + assert len(engine.backend.received_commands) == 3 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == Allocate + assert engine.backend.received_commands[2].gate == FlushGate() + + +def test_read_qasm2_gate_def(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; + +gate my_cu1(lambda) a,b +{ + u1(lambda/2) a; + cx a,b; + u1(-lambda/2) b; + cx a,b; + u1(lambda/2) b; +} +gate empty() a,b +{ +} + +empty q[0],q[1]; +my_cu1(1) q[0],q[1]; +""" + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert not bits_map + assert len(eng.backend.received_commands) == 7 + assert eng.backend.received_commands[0].gate == Allocate + assert eng.backend.received_commands[1].gate == Allocate + + a, b = qubits_map['q'] + + cmd = eng.backend.received_commands[2] + assert cmd.gate == Rz(0.5) + assert [qubit for qureg in cmd.qubits for qubit in qureg] == [a] + + cmd = eng.backend.received_commands[3] + assert cmd.gate == X + assert cmd.control_qubits == [a] + assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] + + cmd = eng.backend.received_commands[4] + assert cmd.gate == Rz(-0.5) + assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] + + cmd = eng.backend.received_commands[5] + assert cmd.gate == X + assert cmd.control_qubits == [a] + assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] + + cmd = eng.backend.received_commands[6] + assert cmd.gate == Rz(0.5) + assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] + +def test_read_qasm2_file(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c0[1]; +""" + + with tempfile.NamedTemporaryFile() as fd: + fd.write((qasm_str + '\n').encode()) + fd.seek(0) + engine = deepcopy(eng) + qubits_map, bits_map = read_qasm_file(engine, fd.name) + assert bits_map == {'c0': [None]} + assert len(engine.backend.received_commands) == 2 + assert engine.backend.received_commands[0].gate == Allocate + assert engine.backend.received_commands[1].gate == Allocate + + +# ============================================================================== +# OpenQASM 3.0 tests (experimental) + + +@pytest.mark.xfail +def test_read_qasm3_empty(eng): + qasm_str = """ +OPENQASM 3.0; +include "stdgates.qasm"; +""" + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert not eng.backend.received_commands diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py new file mode 100644 index 00000000..e4836ff5 --- /dev/null +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -0,0 +1,39 @@ +from projectq.ops import (Barrier, H, S, Sdagger, T, Tdagger, X, Y, Z, Rx, Ry, + Rz, H, Swap, Toffoli, C, CNOT, U2, U3) + +# ============================================================================== +# Conversion map between Qiskit gate names and ProjectQ gates + +gates_conv_table = { + 'barrier': Barrier, + 'h': H, + 's': S, + 'sdg': Sdagger, + 't': T, + 'tdg': Tdagger, + 'x': X, + 'y': Y, + 'z': Z, + 'swap': Swap, + 'rx': lambda a: Rx(a), + 'ry': lambda a: Ry(a), + 'rz': lambda a: Rz(a), + 'u1': lambda a: Rz(a), + 'u2': lambda p, l: U2(p, l), + 'u3': lambda t, p, l: U3(t, p, l), + 'phase': lambda a: Rz(a), + + # Controlled gates + 'ch': C(H), + 'cx': CNOT, + 'cy': C(Y), + 'cz': C(Z), + 'cswap': C(Swap), + 'crz': lambda a: C(Rz(a)), + 'cu1': lambda a: C(Rz(a)), + 'cu2': lambda p, l: C(U2(p, l)), + 'cu3': lambda t, p, l: C(U3(t, p, l)), + + # Doubly-controlled gates + "ccx": Toffoli, +} From 05884b8b4cad4efc8b14626cc279afa70e1d0e1a Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 10 Nov 2020 16:04:27 +0100 Subject: [PATCH 04/42] Fix some issues with measurements and conditional expressions - Classical register now are initialised to 0 - Fixed bug with measurements of registers with size > 1 - Fixed issue with conditional expressions and registers with size > 1 --- projectq/libs/qasm/_parse_qasm_qiskit.py | 19 ++++-- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 64 ++++++++++++++++++- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 3ca2f35f..60c6dd13 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -15,7 +15,7 @@ from projectq.ops import All, Measure, ControlledGate, SwapGate -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, ClassicalRegister, Clbit from ._qiskit_conv import gates_conv_table @@ -68,7 +68,8 @@ def apply_op(eng, gate, qubits, bits, bits_map): eng.flush() for idx, bit in enumerate(bits): - bits_map[bit.register.name][idx] = bool(qubits[idx]) + assert isinstance(bit, Clbit) + bits_map[bit.register.name][bit.index] = bool(qubits[idx]) else: if gate.name not in gates_conv_table: if not gate._definition: @@ -93,9 +94,14 @@ def apply_op(eng, gate, qubits, bits, bits_map): # OpenQASM 2.0 cbit, value = gate.condition - # Note: apparently, it is illegal to write if (c0[0] == 1) - # so we always assume that index == 0... - if bits_map[cbit.name][0] == value: + if cbit.size == 1: + cbit_value = bits_map[cbit.name][0] + else: + cbit_value = 0 + for bit in bits_map[cbit.name]: + cbit_value = (cbit_value << 1) | bit + + if cbit_value == value: apply_gate(gate_projectq, qubits) else: apply_gate(gate_projectq, qubits) @@ -120,7 +126,7 @@ def _convert_qiskit_circuit(eng, qc): qureg.name: eng.allocate_qureg(qureg.size) for qureg in qc.qregs } - bits_map = {bit.name: [None] * bit.size for bit in qc.cregs} + bits_map = {bit.name: [False] * bit.size for bit in qc.cregs} # Convert all the gates to ProjectQ commands for gate, quregs, bits in qc.data: @@ -131,6 +137,7 @@ def _convert_qiskit_circuit(eng, qc): return qubits_map, bits_map + def read_qasm_str(eng, qasm_str): """ Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index f7540270..61ee344d 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -144,7 +144,7 @@ def test_read_qasm2_allocation(eng): engine = deepcopy(eng) qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert bits_map == {'c': [None], 'd': [None, None]} + assert bits_map == {'c': [False], 'd': [False, False]} assert len(engine.backend.received_commands) == 3 assert all(cmd.gate == Allocate for cmd in engine.backend.received_commands) @@ -321,6 +321,7 @@ def test_read_qasm2_gate_def(eng): assert cmd.gate == Rz(0.5) assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] + def test_read_qasm2_file(eng): qasm_str = """ OPENQASM 2.0; @@ -334,12 +335,71 @@ def test_read_qasm2_file(eng): fd.seek(0) engine = deepcopy(eng) qubits_map, bits_map = read_qasm_file(engine, fd.name) - assert bits_map == {'c0': [None]} + assert bits_map == {'c0': [False]} assert len(engine.backend.received_commands) == 2 assert engine.backend.received_commands[0].gate == Allocate assert engine.backend.received_commands[1].gate == Allocate +def test_qasm_qft2(eng): + qasm_str = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg a[2]; +creg b[2]; +measure a -> b; +qreg q[3]; +creg c[3]; +// optional post-rotation for state tomography +gate post q { } +u3(0.3,0.2,0.1) q[0]; +h q[1]; +cx q[1],q[2]; +barrier q; +cx q[0],q[1]; +h q[0]; +measure q[0] -> c[0]; +measure q[1] -> c[1]; +if(c==1) z q[2]; +if(c==2) x q[2]; +if(c==3) y q[2]; +if(c==4) z q[1]; +if(c==5) x q[1]; +if(c==6) y q[1]; +if(c==7) z q[0]; +if(c==8) x q[0]; +post q[2]; +measure q[2] -> c[2]; + """ + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q', 'a'} == set(qubits_map) + assert len(qubits_map['a']) == 2 + assert len(qubits_map['q']) == 3 + assert bits_map == {'b': [True, True], 'c': [True, True, True]} + + assert len([ + cmd for cmd in eng.backend.received_commands if cmd.gate == Allocate + ]) == 5 + assert len([ + cmd for cmd in eng.backend.received_commands + if isinstance(cmd.gate, U3) + ]) == 1 + assert len([cmd for cmd in eng.backend.received_commands + if cmd.gate == X]) == 2 + cmds = [cmd for cmd in eng.backend.received_commands if cmd.gate == Y] + assert len(cmds) == 1 + assert ([qubits_map['q'][1] + ] == [qubit for qureg in cmds[0].qubits for qubit in qureg]) + assert len([cmd for cmd in eng.backend.received_commands + if cmd.gate == Z]) == 0 + assert len([cmd for cmd in eng.backend.received_commands + if cmd.gate == H]) == 2 + cmds = [cmd for cmd in eng.backend.received_commands if cmd.gate == Barrier] + assert len(cmds) == 1 + assert len([qubit for qureg in cmds[0].qubits for qubit in qureg]) == 3 + + # ============================================================================== # OpenQASM 3.0 tests (experimental) From b874ffd5112528a64fdc462e6e8c5cb9fc94547b Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 10 Nov 2020 18:29:15 +0100 Subject: [PATCH 05/42] Move apply_gate to its own file --- projectq/libs/qasm/_parse_qasm_qiskit.py | 26 +-------- projectq/libs/qasm/_utils.py | 74 ++++++++++++++++++++++++ projectq/libs/qasm/_utils_test.py | 68 ++++++++++++++++++++++ 3 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 projectq/libs/qasm/_utils.py create mode 100644 projectq/libs/qasm/_utils_test.py diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 60c6dd13..5d2b2203 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -18,34 +18,11 @@ from qiskit.circuit import QuantumCircuit, ClassicalRegister, Clbit from ._qiskit_conv import gates_conv_table +from ._utils import apply_gate, OpaqueGate # ============================================================================== -def apply_gate(gate, qubits): - """ - Apply a gate to some qubits while separating control and target qubits. - - - Args: - gate (BasicGate): Instance of a ProjectQ gate - qubits (list): List of ProjectQ qubits the gate applies to. - """ - if isinstance(gate, ControlledGate): - ctrls = qubits[:gate._n] - qubits = qubits[gate._n:] - if isinstance(gate._gate, SwapGate): - assert len(qubits) == 2 - gate | (ctrls, qubits[0], qubits[1]) - else: - gate | (ctrls, qubits) - elif isinstance(gate, SwapGate): - assert len(qubits) == 2 - gate | (qubits[0], qubits[1]) - else: - gate | qubits - - def apply_op(eng, gate, qubits, bits, bits_map): """ Apply a qiskit operation. @@ -73,6 +50,7 @@ def apply_op(eng, gate, qubits, bits, bits_map): else: if gate.name not in gates_conv_table: if not gate._definition: + # TODO: This will silently discard opaque gates... return gate_args = {gate._definition.qregs[0].name: qubits} diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py new file mode 100644 index 00000000..8d4c4fad --- /dev/null +++ b/projectq/libs/qasm/_utils.py @@ -0,0 +1,74 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +"""Some helper utilities.""" + +from projectq.ops import ControlledGate, SwapGate, BasicGate + +# ============================================================================== + + +class OpaqueGate(BasicGate): + def __init__(self, name, params): + """ + Constructor + + Args: + name (str): Name/type of gat + params (list,tuple): Parameter for the gate (may be empty) + """ + + super().__init__() + self.name = name + self.params = params + + def __str__(self): + """ + String conversion. + """ + # TODO: This is a bit crude... + if self.params: + return 'Opaque({})({})'.format(self.name, ','.join(self.params)) + return 'Opaque({})'.format(self.name) + + +# ============================================================================== + + +def apply_gate(gate, qubits): + """ + Apply a gate to some qubits while separating control and target qubits. + + + Args: + gate (BasicGate): Instance of a ProjectQ gate + qubits (list): List of ProjectQ qubits the gate applies to. + """ + # pylint: disable = protected-access,pointless-statement + + if isinstance(gate, ControlledGate): + ctrls = qubits[:gate._n] + qubits = qubits[gate._n:] + if isinstance(gate._gate, SwapGate): + assert len(qubits) == 2 + gate | (ctrls, qubits[0], qubits[1]) + else: + gate | (ctrls, qubits) + elif isinstance(gate, SwapGate): + assert len(qubits) == 2 + gate | (qubits[0], qubits[1]) + else: + gate | qubits + + +# ============================================================================== diff --git a/projectq/libs/qasm/_utils_test.py b/projectq/libs/qasm/_utils_test.py new file mode 100644 index 00000000..6eb9ea11 --- /dev/null +++ b/projectq/libs/qasm/_utils_test.py @@ -0,0 +1,68 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +"""Tests for projectq.libs.qasm._utils.py.""" + +import pytest + +from projectq.types import WeakQubitRef +from projectq.cengines import DummyEngine +from projectq.ops import (X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, + U2, U3, Swap, Toffoli, Barrier, C) +from ._utils import apply_gate, OpaqueGate + +# ============================================================================== + + +def test_opaque_gate(): + gate = OpaqueGate('my_gate', None) + assert gate.name == 'my_gate' + assert not gate.params + assert str(gate) == 'Opaque(my_gate)' + + gate = OpaqueGate('my_gate', ('lambda', 'alpha')) + assert gate.name == 'my_gate' + assert gate.params == ('lambda', 'alpha') + + assert str(gate) == 'Opaque(my_gate)(lambda,alpha)' + + +# ============================================================================== + + +@pytest.mark.parametrize( + 'gate, n_qubits', (list( + map(lambda x: + (x, 1), [ + X, Y, Z, S, Sdagger, T, Tdagger, H, Barrier, + Ph(1.12), + Rx(1.12), + Ry(1.12), + Rz(1.12), + R(1.12), + U2(1.12, 1.12), + U3(1.12, 1.12, 1.12), + ])) + list(map(lambda x: + (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) + + list(map(lambda x: + (x, 3), [Toffoli, C(Swap), Barrier])) + + list(map(lambda x: (x, 10), [Barrier]))), + ids=str) +def test_apply_gate(gate, n_qubits): + backend = DummyEngine() + backend.is_last_engine = True + + gate.engine = backend + qubits = [WeakQubitRef(backend, idx) for idx in range(n_qubits)] + + apply_gate(gate, qubits) From 1a14639e2a98c0600d82cdb7790bbc4839d8d11e Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 10 Nov 2020 16:05:01 +0100 Subject: [PATCH 06/42] Add OpenQASM parser based on Pyparsing + cleanup qiskit code --- projectq/libs/qasm/__init__.py | 30 +- projectq/libs/qasm/_parse_qasm_pyparsing.py | 995 ++++++++++++++++++ projectq/libs/qasm/_parse_qasm_qiskit.py | 39 +- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 415 -------- projectq/libs/qasm/_pyparsing_expr.py | 259 +++++ 5 files changed, 1296 insertions(+), 442 deletions(-) create mode 100644 projectq/libs/qasm/_parse_qasm_pyparsing.py delete mode 100644 projectq/libs/qasm/_parse_qasm_qiskit_test.py create mode 100644 projectq/libs/qasm/_pyparsing_expr.py diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index f39cabd7..016e5998 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -18,20 +18,22 @@ try: from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str except ImportError: # pragma: no cover - import warnings + try: + from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str + except ImportError as e: + import warnings + err = ('Unable to import either qiskit or pyparsing\n' + 'Please install either of them (e.g. using the ' + 'command python -m pip install qiskit') - err = ('Unable to import qiskit\n' - 'Please install it (e.g. using the command: ' - 'python -m pip install qiskit') + warnings.warn(err + '\n' + 'The provided read_qasm_* functions will systematically' + 'raise a RuntimeError') - warnings.warn(err + 'c\n' - 'The provided read_qasm_* functions will systematically' - 'raise a RuntimeError') + def read_qasm_file(eng, filename): + # pylint: disable=unused-argument + raise RuntimeError(err) - def read_qasm_file(eng, filename): - # pylint: disable=unused-argument - raise RuntimeError(err) - - def read_qasm_str(eng, qasm_str): - # pylint: disable=unused-argument - raise RuntimeError(err) + def read_qasm_str(eng, qasm_str): + # pylint: disable=unused-argument + raise RuntimeError(err) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py new file mode 100644 index 00000000..a2e88b3f --- /dev/null +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -0,0 +1,995 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +""" +Contains the main engine of every compiler engine pipeline, called MainEngine. +""" + +from copy import deepcopy +import operator as op +from pyparsing import (Literal, Word, Group, Or, CharsNotIn, Optional, + nestedExpr, Suppress, removeQuotes, Empty, + cppStyleComment, cStyleComment, dblQuotedString, alphas, + alphanums, pyparsing_common) + +from projectq.ops import All, Measure + +from ._pyparsing_expr import eval_expr +from ._qiskit_conv import gates_conv_table +from ._utils import apply_gate, OpaqueGate + +# ============================================================================== + +_QISKIT_VARS = dict() +_BITS_VARS = dict() +_CUSTOM_GATES = dict() +_OPAQUE_GATES = dict() + + +class CommonTokens: + """ + Some general tokens + """ + # pylint: disable = too-few-public-methods + + int_v = pyparsing_common.signed_integer + float_v = pyparsing_common.fnumber + string_v = dblQuotedString().setParseAction(removeQuotes) + + # variable names + cname = Word(alphas + "_", alphanums + '_') + + lbra, rbra = map(Suppress, "[]") + + # variable expressions (e.g. qubit[0]) + variable_expr = Group(cname + Optional(lbra + int_v + rbra)) + + +# ============================================================================== + + +class QASMVersionOp: + """ + OpenQASM version. + """ + def __init__(self, toks): + self.version = toks[0] + + def eval(self, _): + """ + Evaluate a QASMVersionOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + # pylint: disable=unused-argument + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + return 'QASMVersionOp({})'.format(self.version) + + +class IncludeOp: + """ + Include file operation. + """ + def __init__(self, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + self.fname = toks[0] + + def eval(self, _): + """ + Evaluate a IncludeOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if self.fname in 'qelib1.inc, stdlib.inc': + pass + else: # pragma: nocover + raise RuntimeError('Invalid cannot read: {}! (unsupported)'.format( + self.fname)) + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + return 'IncludeOp({})'.format(self.fname) + + +# ============================================================================== + + +class QubitProxy: + """ + Qubit access proxy class + """ + def __init__(self, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + if len(toks) == 2: + self.name = str(toks[0]) + self.index = int(toks[1]) + else: + self.name = toks[0] + self.index = None + + def eval(self, _): + """ + Evaluate a QubitProxy + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if self.index is not None: + return _QISKIT_VARS[self.name][self.index] + return _QISKIT_VARS[self.name] + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + if self.index is not None: + return 'Qubit({}[{}])'.format(self.name, self.index) + return 'Qubit({})'.format(self.name) + + +# ============================================================================== + + +class VarDeclOp: + """ + Variable declaration operation. + """ + + # pylint: disable = too-few-public-methods + + def __init__(self, type_t, nbits, name, init): + """ + Constructor + + Args: + type_t (str): Type of variable + nbits (int): Number of bits in variable + name (str): Name of variable + init (str): Initialization expression for variable + """ + self.type_t = type_t + self.nbits = nbits + self.name = name + self.init = init + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + if self.init: + return "{}({}, {}, {}) = {}".format(self.__class__.__name__, + self.type_t, self.nbits, + self.name, self.init) + + return "{}({}, {}, {})".format(self.__class__.__name__, self.type_t, + self.nbits, self.name) + + +# ------------------------------------------------------------------------------ + + +class QVarOp(VarDeclOp): + """ + Quantum variable declaration operation. + """ + + # pylint: disable = too-few-public-methods + + def eval(self, eng): + """ + Evaluate a QVarOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if self.name not in _QISKIT_VARS: + _QISKIT_VARS[self.name] = eng.allocate_qureg(self.nbits) + else: # pragma: nocover + raise RuntimeError('Variable exist already: {}'.format(self.name)) + + +# ------------------------------------------------------------------------------ + + +class CVarOp(VarDeclOp): + """ + Classical variable declaration operation. + """ + + # pylint: disable = too-few-public-methods + + def eval(self, _): + """ + Evaluate a CVarOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + # NB: here we are cheating a bit, since we are ignoring the number of + # bits, except for classical registers... + if self.name not in _BITS_VARS: + init = 0 + if self.init: # pragma: nocover + init = parse_expr(self.init) + + # The followings are OpenQASM 3.0 + if self.type_t in ('const', 'float', 'fixed', + 'angle'): # pragma: nocover + _BITS_VARS[self.name] = float(init) + elif self.type_t in ('int', 'uint'): # pragma: nocover + _BITS_VARS[self.name] = int(init) + elif self.type_t == 'bool': # pragma: nocover + _BITS_VARS[self.name] = bool(init) + else: + # bit, creg + assert self.init is None + _BITS_VARS[self.name] = [False] * self.nbits + else: # pragma: nocover + raise RuntimeError('Variable exist already: {}'.format(self.name)) + + +# ============================================================================== + + +class GateDefOp: + """ + Operation representing a gate definition. + """ + def __init__(self, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + self.name = toks[1] + self.params = [t[0] for t in toks[2]] + self.qparams = list(toks[3]) + self.body = list(toks[4]) + if not self.body: + self.body = [] + + def eval(self, _): + """ + Evaluate a GateDefOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + _CUSTOM_GATES[self.name] = (self.params, self.qparams, self.body) + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + return "GateDefOp({}, {}, {})\n\t{}".format(self.name, self.params, + self.qparams, self.body) + + +# ============================================================================== + + +class MeasureOp: + """ Measurement operations (OpenQASM 2.0 & 3.0) """ + def __init__(self, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + if toks[1] == 'measure': # pragma: nocover + # OpenQASM 3.0 + self.qubits = QubitProxy(toks[2]) + self.bits = tuple(toks[0]) + elif toks[0] == 'measure': + # OpenQASM 2.0 + self.qubits = QubitProxy(toks[1]) + self.bits = tuple(toks[2]) + else: # pragma: nocover + raise RuntimeError('Unable to normalize measurement operation!') + + def eval(self, eng): + """ + Evaluate a MeasureOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + # pylint: disable = pointless-statement, expression-not-assigned + # pylint: disable = global-statement + + global _BITS_VARS + + qubits = self.qubits.eval(eng) + if not isinstance(qubits, list): + Measure | qubits + else: + All(Measure) | qubits + eng.flush() + + if not isinstance(qubits, list): + if len(self.bits) == 1: + _BITS_VARS[self.bits[0]][0] = bool(qubits) + else: + _BITS_VARS[self.bits[0]][self.bits[1]] = bool(qubits) + else: + bits = _BITS_VARS[self.bits[0]] + assert len(qubits) == len(bits) + for idx, qubit in enumerate(qubits): + bits[idx] = bool(qubit) + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + return 'MeasureOp({}, {})'.format(self.qubits, self.bits) + + +# ------------------------------------------------------------------------------ + + +class OpaqueDefOp: + """ + Opaque gate definition operation. + """ + def __init__(self, toks): + """ + Constructor + + Args: + name (str): Name/type of gat + params (list,tuple): Parameter for the gate (may be empty) + qubits (list): List of target qubits + """ + self.name = toks[1] + self.params = toks[2] + + def eval(self, _): + """ + Evaluate a OpaqueDefOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + _OPAQUE_GATES[self.name] = OpaqueGate(self.name, self.params) + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + if self.params: + return 'OpaqueOp({}, {})'.format(self.name, self.params) + return 'OpaqueOp({})'.format(self.name) + + +# ------------------------------------------------------------------------------ + + +class GateOp: + """ + Gate applied to qubits operation. + """ + def __init__(self, s, loc, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + self.name = toks[0].lower() + if len(toks) == 2: + self.params = [] + self.qubits = [QubitProxy(qubit) for qubit in toks[1]] + else: + param_str = s[loc:s.find(';', loc)] + self.params = param_str[param_str.find('(') + + 1:param_str.rfind(')')].split(',') + self.qubits = [QubitProxy(qubit) for qubit in toks[2]] + + def eval(self, eng): + """ + Evaluate a GateOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if self.name in gates_conv_table: + if self.params: + gate = gates_conv_table[self.name]( + *[parse_expr(p) for p in self.params]) + else: + gate = gates_conv_table[self.name] + + qubits = [] + for qureg in [qubit.eval(eng) for qubit in self.qubits]: + try: + qubits.extend(qureg) + except TypeError: + qubits.append(qureg) + + apply_gate(gate, qubits) + elif self.name in _OPAQUE_GATES: + qubits = [] + for qureg in [qubit.eval(eng) for qubit in self.qubits]: + try: + qubits.extend(qureg) + except TypeError: + qubits.append(qureg) + + apply_gate(_OPAQUE_GATES[self.name], qubits) + elif self.name in _CUSTOM_GATES: + gate_params, gate_qparams, gate_body = _CUSTOM_GATES[self.name] + + if self.params: + assert gate_params + assert len(self.params) == len(gate_params) + + params_map = { + p_param: p_var + for p_var, p_param in zip(self.params, gate_params) + } + qparams_map = { + q_param: q_var + for q_var, q_param in zip(self.qubits, gate_qparams) + } + + # NB: this is a hack... + # Will probably not work for multiple expansions of + # variables... + # pylint: disable = global-statement + global _BITS_VARS + bits_vars_bak = deepcopy(_BITS_VARS) + _BITS_VARS.update(params_map) + + for gate in gate_body: + # Map gate parameters and quantum parameters to real variables + gate.qubits = [qparams_map[q.name] for q in gate.qubits] + gate.eval(eng) + _BITS_VARS = bits_vars_bak + else: # pragma: nocover + if self.params: + gate_str = '{}({}) | {}'.format(self.name, self.params, + self.qubits) + else: + gate_str = '{} | {}'.format(self.name, self.qubits) + raise RuntimeError('Unknown gate: {}'.format(gate_str)) + + def __repr__(self): # pragma: nocover + """ + Mainly for debugging + """ + return 'GateOp({}, {}, {})'.format(self.name, self.params, self.qubits) + + +# ============================================================================== + + +class AssignOp: # pragma: nocover + """ + Variable assignment operation (OpenQASM 3.0 only) + """ + def __init__(self, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + self.var = toks[0] + self.value = toks[1][0] + + def eval(self, _): + """ + Evaluate a AssignOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if self.var in _BITS_VARS: + # Assigning to creg or bit is not supported yet... + assert not isinstance(_BITS_VARS[self.var], list) + value = parse_expr(self.value) + _BITS_VARS[self.var] = value + else: + raise RuntimeError('The variable {} is not defined!'.format( + self.var)) + return 0 + + def __repr__(self): + """ + Mainly for debugging + """ + return 'AssignOp({},{})'.format(self.var, self.value) + + +# ============================================================================== + + +def _parse_if_conditional(if_str): + # pylint: disable = invalid-name + start = if_str.find('(') + 1 + + level = 1 + for idx, ch in enumerate(if_str[start:]): + if ch == '(': # pragma: nocover + # NB: only relevant for OpenQASM 3.0 + level += 1 + elif ch == ')': + level -= 1 + if level == 0: + return if_str[start:start + idx] + raise RuntimeError( + 'Unbalanced parantheses in {}'.format(if_str)) # pragma: nocover + + +class IfOp: + """ + Operation representing a conditional expression (if-expr). + """ + + greater = Literal('>').addParseAction(lambda: op.gt) + greater_equal = Literal('>=').addParseAction(lambda: op.ge) + less = Literal('<').addParseAction(lambda: op.lt) + less_equal = Literal('<=').addParseAction(lambda: op.le) + equal = Literal('==').addParseAction(lambda: op.eq) + not_equal = Literal('!=').addParseAction(lambda: op.ne) + + logical_bin_op = Or( + [greater, greater_equal, less, less_equal, equal, not_equal]) + + cond_expr = (CommonTokens.variable_expr.copy() + logical_bin_op + + CharsNotIn('()')) + + def __init__(self, if_str, loc, toks): + """ + Constructor + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + # pylint: disable=unused-argument + + cond = _parse_if_conditional(if_str[loc:]) + res = IfOp.cond_expr.parseString(cond, parseAll=True) + self.binary_op = res[1] + self.comp_expr = res[2] + self.body = toks[2:] + + if len(res[0]) == 1: + self.bit = res[0][0] + else: # pragma: nocover + # OpenQASM >= 3.0 only + name, index = res[0] + self.bit = (name, index) + + def eval(self, eng): + """ + Evaluate a IfOp + + Args: + eng (projectq.BasicEngine): ProjectQ MainEngine to use + """ + if isinstance(self.bit, tuple): # pragma: nocover + # OpenQASM >= 3.0 only + bit = _BITS_VARS[self.bit[0]][self.bit[1]] + else: + bit_val = _BITS_VARS[self.bit] + if isinstance(bit_val, list) and len(bit_val) > 1: + bit = 0 + for bit_el in reversed(bit_val): + bit = (bit << 1) | bit_el + else: + bit = bit_val[0] + + if self.binary_op(bit, parse_expr(self.comp_expr)): + for gate in self.body: + gate.eval(eng) + + def __repr__(self): # pragma: nocover + return "IfExpr({} {} {}) {{ {} }}".format(self.bit, self.binary_op, + self.comp_expr, self.body) + + +# ============================================================================== + + +def create_var_decl(toks): + """ + Callback function to create either a classical or quantum variable + operation. + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + + type_t = toks[0] + + names = [] + nbits = 1 + body = None + + if len(toks) == 3: # pragma: nocover + # OpenQASM >= 3.0 only + # qubit[NN] var, var, var; + # fixed[7, 25] num; + names = toks[2] + nbits = toks[1] + var_list = [] + + def _get_name(name): + return name + + def _get_nbits(_): + return nbits + elif len(toks) == 2: + # qubit qa, qb[2], qc[3]; + names = toks[1] + + def _get_name(name): + return name[0] + + def _get_nbits(name): + if len(name) == 1: # pragma: nocover + # OpenQASM >= 3.0 only + return 1 + return name[1] + else: # pragma: nocover + # OpenQASM >= 3.0 only + # const myvar = 1234; + names = [toks[2]] + nbits = toks[1] + body = toks[3][0] + assert len(toks) == 4 + assert len(names) == 1 + + def _get_name(name): + return name + + def _get_nbits(_): + return nbits + + var_list = [] + for name in names: + if type_t in ('const', 'creg', 'bit', 'uint', 'int', 'fixed', 'float', + 'angle', 'bool'): + var_list.append( + CVarOp(type_t, _get_nbits(name), _get_name(name), body)) + elif body is None: + var_list.append( + QVarOp(type_t, _get_nbits(name), _get_name(name), body)) + else: # pragma: nocover + raise RuntimeError( + 'Initializer for quantum variable is unsupported!') + + return var_list + + +def parse_expr(expr_str): + """ + Callback function to parse an (mathematical) expression. + + Args: + expr_str (str): Expression to evaluate + """ + return eval_expr(expr_str, _BITS_VARS) + + +# ============================================================================== + + +class QiskitParser: + """ + Qiskit parser class + """ + def __init__(self): + # pylint: disable = too-many-locals + # ---------------------------------------------------------------------- + # Punctuation marks + + lpar, rpar, lbra, rbra, lbrace, rbrace = map(Suppress, "()[]{}") + equal_sign, comma, end = map(Suppress, "=,;") + + # ---------------------------------------------------------------------- + # Quantum and classical types + + qubit_t = Literal("qubit") ^ Literal("qreg") + bit_t = Literal("bit") ^ Literal("creg") + bool_t = Literal("bool") + const_t = Literal("const") + int_t = Literal("int") + uint_t = Literal("uint") + angle_t = Literal("angle") + fixed_t = Literal("fixed") + float_t = Literal("float") + # length_t = Literal("length") + # stretch_t = Literal("stretch") + + # ---------------------------------------------------------------------- + # Other core types + + cname = CommonTokens.cname.copy() + float_v = CommonTokens.float_v.copy() + int_v = CommonTokens.int_v.copy() + string_v = CommonTokens.string_v.copy() + + # ---------------------------------------------------------------------- + # Variable type matching + + # Only match an exact type + var_type = Or( + [qubit_t, bit_t, bool_t, const_t, int_t, uint_t, angle_t, float_t]) + + # Match a type or an array of type (e.g. int vs int[10]) + type_t = (var_type + Optional(lbra + int_v + rbra, default=1)) | ( + fixed_t + Group(lbra + int_v + comma + int_v + rbra)) + + # ---------------------------------------------------------------------- + # (mathematical) expressions + + expr = CharsNotIn(',;') + variable_expr = CommonTokens.variable_expr + + # ---------------------------------------------------------------------- + # Variable declarations + + # e.g. qubit[10] qr, qs / int[5] i, j + variable_decl_const_bits = type_t + Group(cname + (comma + cname)[...]) + + # e.g. qubit qr[10], qs[2] / int i[5], j[10] + variable_decl_var_bits = var_type + Group(variable_expr + + (comma + variable_expr)[...]) + + # e.g. int[10] i = 5; + variable_decl_assign = type_t + cname + equal_sign + Group(expr) + + # Putting it all together + variable_decl_statement = Or([ + variable_decl_const_bits, variable_decl_var_bits, + variable_decl_assign + ]).addParseAction(create_var_decl) + + # ---------------------------------------------------------------------- + # Gate operations + + gate_op_no_param = cname + Group(variable_expr + + (comma + variable_expr)[...]) + gate_op_w_param = cname + Group( + nestedExpr(ignoreExpr=comma)) + Group(variable_expr + + (comma + variable_expr)[...]) + + # ---------------------------------- + # Measure gate operations + + measure_op_qasm2 = Literal("measure") + variable_expr + Suppress( + "->") + variable_expr + measure_op_qasm3 = variable_expr + equal_sign + Literal( + "measure") + variable_expr + + measure_op = (measure_op_qasm2 + ^ measure_op_qasm3).addParseAction(MeasureOp) + + # Putting it all together + gate_op = Or([gate_op_no_param, gate_op_w_param, + measure_op]).addParseAction(GateOp) + + # ---------------------------------------------------------------------- + # If expressions + + if_expr_qasm2 = Literal("if") + nestedExpr( + ignoreExpr=comma) + gate_op + end + + # NB: not exactly 100% OpenQASM 3.0 conformant... + if_expr_qasm3 = (Literal("if") + nestedExpr(ignoreExpr=comma) + + (lbrace + Group(gate_op + end)[1, ...] + rbrace)) + if_expr = (if_expr_qasm2 ^ if_expr_qasm3).addParseAction(IfOp) + + assign_op = (cname + equal_sign + Group(expr)).addParseAction(AssignOp) + + # ---------------------------------------------------------------------- + + # NB: this is not restrictive enough and may parse some invalid code + # such as: + # gate g a + # { + # U(0,0,0) a[0]; // <- indexing of a is forbidden + # } + + param_decl_qasm2 = cname + param_decl_qasm3 = type_t + Suppress(":") + cname + + param_decl = Group(param_decl_qasm2 ^ param_decl_qasm3) + + qargs_list = Group(cname + (comma + cname)[...]) + + gate_def_no_args = (lpar + rpar)[...] + Group(Empty()) + qargs_list + gate_def_w_args = (lpar + Group(param_decl + + (comma + param_decl)[...]) + rpar + + qargs_list) + gate_def_expr = (Literal("gate") + cname + + (gate_def_no_args ^ gate_def_w_args) + lbrace + Group( + (gate_op + end)[...]) + + rbrace).addParseAction(GateDefOp) + + # ---------------------------------- + # Opaque gate declarations operations + + opaque_def_expr = (Literal("opaque") + cname + + (gate_def_no_args ^ gate_def_w_args) + + end).addParseAction(OpaqueDefOp) + + # ---------------------------------------------------------------------- + # Control related expressions (OpenQASM 3.0) + + # control_var_type = Or([length_t, stretch_t + int_v[...]]) + + # lengthof_arg = Word(alphanums + '_+-*/%{}[]')[1, ...] + # lengthof_op = Literal("lengthof") + lpar + lengthof_arg + rpar + + # units = Word(alphas) + # control_variable_decl = control_var_type + cname + # control_variable_decl_assign = ( + # control_var_type + cname + equal_sign + + # ((pyparsing_common.number + units) | lengthof_op | expr)) + + # control_variable_decl_statement = Group( + # Or([control_variable_decl, control_variable_decl_assign])) + + # control_func_arg = Word(alphanums + '_+-*/%')[1, ...] + # control_func_op = Literal("rotary") + + # rotary_op = (control_func_op + lpar + control_func_arg + rpar + lbra + # + ((float_v + units) ^ control_func_arg) + rbra + + # variable_expr + (comma + variable_expr)[...]) + + # delay_arg = Word(alphanums + '_+-*/%')[1, ...] + # delay_op = Literal("delay") + lbra + delay_arg + rbra + Optional( + # variable_expr + (comma + variable_expr)[...]) + + # control_op = Group(Or([rotary_op, delay_op])) + + # ---------------------------------------------------------------------- + + header = (Suppress("OPENQASM") + + (int_v ^ float_v).addParseAction(QASMVersionOp) + end) + + include = (Suppress("include") + string_v.addParseAction(IncludeOp) + + end) + + statement = ( + (measure_op + end) + | if_expr + | (gate_def_expr) + | opaque_def_expr + | (variable_decl_statement + end) + # | (control_variable_decl_statement + + # end).addParseAction(lambda toks: []) + | (assign_op + end) + | (gate_op + end) + # | (control_op + end).addParseAction(lambda toks: []) + ) + + self.parser = header + include[...] + statement[...] + self.parser.ignore(cppStyleComment) + self.parser.ignore(cStyleComment) + + def parse_str(self, qasm_str): + """ + Parse a QASM string + + Args: + qasm_str (str): QASM string + """ + return self.parser.parseString(qasm_str, parseAll=True) + + def parse_file(self, fname): + """ + Parse a QASM file + + Args: + fname (str): Filename + """ + return self.parser.parseFile(fname, parseAll=True) + + +def _reset(): + """ + Reset internal variables + """ + # pylint: disable = invalid-name, global-statement + global _QISKIT_VARS, _BITS_VARS, _CUSTOM_GATES, _OPAQUE_GATES + + _QISKIT_VARS = dict() + _BITS_VARS = dict() + _CUSTOM_GATES = dict() + _OPAQUE_GATES = dict() + + +# ============================================================================== + +parser = QiskitParser() + +# ------------------------------------------------------------------------------ + + +def read_qasm_str(eng, qasm_str): + """ + Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to + ProjectQ commands. + + This version of the function uses pyparsing in order to parse the *.qasm + file. + + Args: + eng (MainEngine): MainEngine to use for creating qubits and commands. + filename (string): Path to *.qasm file + + Note: + At this time, we support most of OpenQASM 2.0 and some of 3.0, + although the latter is still experimental. + """ + _reset() + for operation in parser.parse_str(qasm_str).asList(): + operation.eval(eng) + return _QISKIT_VARS, _BITS_VARS + + +# ------------------------------------------------------------------------------ + + +def read_qasm_file(eng, filename): + """ + Read an OpenQASM (2.0, 3.0 is experimental) and convert it to ProjectQ + Commands. + + This version of the function uses pyparsing in order to parse the *.qasm + file. + + Args: + eng (MainEngine): MainEngine to use for creating qubits and commands. + filename (string): Path to *.qasm file + + Note: + At this time, we support most of OpenQASM 2.0 and some of 3.0, + although the latter is still experimental. + + Also note that we do not try to enforce 100% conformity to the + OpenQASM standard while parsing QASM code. The parser may allow some + syntax that are actually banned by the standard. + """ + _reset() + for operation in parser.parse_file(filename).asList(): + operation.eval(eng) + return _QISKIT_VARS, _BITS_VARS + + +# ============================================================================== diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 5d2b2203..23fc7773 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -13,12 +13,12 @@ # limitations under the License. """ Define function to read OpenQASM file format (using Qiskit). """ -from projectq.ops import All, Measure, ControlledGate, SwapGate +from projectq.ops import All, Measure -from qiskit.circuit import QuantumCircuit, ClassicalRegister, Clbit +from qiskit.circuit import QuantumCircuit, Clbit from ._qiskit_conv import gates_conv_table -from ._utils import apply_gate, OpaqueGate +from ._utils import apply_gate # ============================================================================== @@ -37,6 +37,7 @@ def apply_op(eng, gate, qubits, bits, bits_map): qubits (list): List of ProjectQ qubits to apply the gate to bits (list): List of classical bits to apply the gate to """ + # pylint: disable = expression-not-assigned, protected-access if bits: # Only measurement gates have clasical bits @@ -76,7 +77,7 @@ def apply_op(eng, gate, qubits, bits, bits_map): cbit_value = bits_map[cbit.name][0] else: cbit_value = 0 - for bit in bits_map[cbit.name]: + for bit in reversed(bits_map[cbit.name]): cbit_value = (cbit_value << 1) | bit if cbit_value == value: @@ -85,7 +86,10 @@ def apply_op(eng, gate, qubits, bits, bits_map): apply_gate(gate_projectq, qubits) -def _convert_qiskit_circuit(eng, qc): +# ============================================================================== + + +def _convert_qiskit_circuit(eng, circuit): """ Convert a QisKit circuit and convert it to ProjectQ commands. @@ -93,7 +97,7 @@ def _convert_qiskit_circuit(eng, qc): Args: eng (MainEngine): MainEngine to use for creating qubits and commands. - qc (qiskit.QuantumCircuit): Quantum circuit to process + circuit (qiskit.QuantumCircuit): Quantum circuit to process Note: At this time, we support most of OpenQASM 2.0 and some of 3.0, @@ -102,12 +106,12 @@ def _convert_qiskit_circuit(eng, qc): # Create maps between qiskit and ProjectQ for qubits and bits qubits_map = { qureg.name: eng.allocate_qureg(qureg.size) - for qureg in qc.qregs + for qureg in circuit.qregs } - bits_map = {bit.name: [False] * bit.size for bit in qc.cregs} + bits_map = {bit.name: [False] * bit.size for bit in circuit.cregs} # Convert all the gates to ProjectQ commands - for gate, quregs, bits in qc.data: + for gate, quregs, bits in circuit.data: apply_op( eng, gate, [qubits_map[qubit.register.name][qubit.index] @@ -116,6 +120,9 @@ def _convert_qiskit_circuit(eng, qc): return qubits_map, bits_map +# ============================================================================== + + def read_qasm_str(eng, qasm_str): """ Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to @@ -132,8 +139,11 @@ def read_qasm_str(eng, qasm_str): At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ - qc = QuantumCircuit.from_qasm_str(qasm_str) - return _convert_qiskit_circuit(eng, qc) + circuit = QuantumCircuit.from_qasm_str(qasm_str) + return _convert_qiskit_circuit(eng, circuit) + + +# ------------------------------------------------------------------------------ def read_qasm_file(eng, filename): @@ -153,6 +163,9 @@ def read_qasm_file(eng, filename): although the latter is still experimental. """ - qc = QuantumCircuit.from_qasm_file(filename) + circuit = QuantumCircuit.from_qasm_file(filename) + + return _convert_qiskit_circuit(eng, circuit) - return _convert_qiskit_circuit(eng, qc) + +# ============================================================================== diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py deleted file mode 100644 index 61ee344d..00000000 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ /dev/null @@ -1,415 +0,0 @@ -# Copyright 2020 ProjectQ-Framework (www.projectq.ch) -# -# 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 -# -# http://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. -"""Tests for projectq.libs.qasm._parse_qasm_qiskit.py.""" - -import pytest - -from projectq.types import WeakQubitRef -from projectq.cengines import MainEngine, DummyEngine, BasicEngine -from projectq.ops import (X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, - U2, U3, Swap, Toffoli, Barrier, All, C, Allocate, - Deallocate, Measure, FlushGate) -from copy import deepcopy -import tempfile - -# ============================================================================== - -try: - from ._parse_qasm_qiskit import (apply_gate, apply_op, read_qasm_file, - read_qasm_str) - no_qiskit = False -except ImportError: - no_qiskit = True - -# ============================================================================== - - -class TestEngine(DummyEngine): - def __init__(self, measurement_result=True): - super().__init__(save_commands=True) - self.is_last_engine = True - self.measurement_result = measurement_result - - def is_available(self, cmd): - return True - - def receive(self, command_list): - for cmd in command_list: - if cmd.gate == Measure: - for qureg in cmd.qubits: - for qubit in qureg: - self.main_engine.set_measurement_result( - qubit, self.measurement_result) - else: - self.received_commands.append(cmd) - - -# ============================================================================== - - -@pytest.fixture -def eng(): - # rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - - return MainEngine(backend=TestEngine(True), engine_list=[]) - - -# ============================================================================== - - -@pytest.mark.skipif(no_qiskit, reason="Could not import Qiskit") -@pytest.mark.parametrize( - 'gate, n_qubits', (list( - map(lambda x: - (x, 1), [ - X, Y, Z, S, Sdagger, T, Tdagger, H, Barrier, - Ph(1.12), - Rx(1.12), - Ry(1.12), - Rz(1.12), - R(1.12) - ])) + list(map(lambda x: - (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) + - list(map(lambda x: - (x, 3), [Toffoli, C(Swap), Barrier])) + - list(map(lambda x: (x, 10), [Barrier]))), - ids=lambda x: str(x)) -def test_apply_gate(gate, n_qubits): - backend = DummyEngine() - backend.is_last_engine = True - - gate.engine = backend - qubits = [WeakQubitRef(backend, idx) for idx in range(n_qubits)] - - apply_gate(gate, qubits) - - -# ============================================================================== -# OpenQASM 2.0 tests - - -def test_read_qasm2_empty(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -""" - - read_qasm_str(eng, qasm_str) - assert not eng.backend.received_commands - - -def test_read_qasm2_allocation(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[1]; -""" - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert not bits_map - assert len(engine.backend.received_commands) == 1 - assert engine.backend.received_commands[0].gate == Allocate - - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[4]; -""" - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert not bits_map - assert len(engine.backend.received_commands) == 4 - assert all(cmd.gate == Allocate for cmd in engine.backend.received_commands) - - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -creg c[1]; -creg d[2]; -""" - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert bits_map == {'c': [False], 'd': [False, False]} - assert len(engine.backend.received_commands) == 3 - assert all(cmd.gate == Allocate for cmd in engine.backend.received_commands) - - -@pytest.mark.parametrize('gate, projectq_gate', - [('x', X), ('y', Y), ('z', Z), ('h', H), ('s', S), - ('sdg', Sdagger), ('t', T), ('tdg', Tdagger), - ('rx(1.12)', Rx(1.12)), ('ry(1.12)', Ry(1.12)), - ('rz(1.12)', Rz(1.12)), ('u1(1.12)', Rz(1.12)), - ('u2(1.12,1.12)', U2(1.12, 1.12)), - ('u3(1.12,1.12,1.12)', U3(1.12, 1.12, 1.12))], - ids=lambda x: str(x)) -def test_read_qasm2_single_qubit_gates(eng, gate, projectq_gate): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[1]; -{} q[0]; -""".format(gate) - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert not bits_map - assert len(engine.backend.received_commands) == 2 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == projectq_gate - - -@pytest.mark.parametrize( - 'gate, projectq_gate', - [ - ('cx', X), - ('cy', Y), - ('cz', Z), - ('ch', H), - ('crz(1.12)', Rz(1.12)), - ('cu1(1.12)', Rz(1.12)), - # ('cu2(1.12,1.12)', U2(1.12, 1.12)), - ('cu3(1.12,1.12,1.12)', U3(1.12, 1.12, 1.12)), - ('swap', Swap) - ], - ids=lambda x: str(x)) -def test_read_qasm2_two_qubit_gates(eng, gate, projectq_gate): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[2]; -{} q[0], q[1]; -""".format(gate) - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert not bits_map - assert len(engine.backend.received_commands) == 3 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == Allocate - cmd = engine.backend.received_commands[2] - assert cmd.gate == projectq_gate - if cmd.gate == Swap: - assert not cmd.control_qubits - assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 2 - else: - assert len(cmd.control_qubits) == 1 - assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 - - -@pytest.mark.parametrize('gate, projectq_gate', [('ccx', X), ('cswap', Swap)], - ids=lambda x: str(x)) -def test_read_qasm2_three_qubit_gates(eng, gate, projectq_gate): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -{} q[0], q[1], q[2]; -""".format(gate) - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert not bits_map - assert len(engine.backend.received_commands) == 4 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == Allocate - assert engine.backend.received_commands[2].gate == Allocate - cmd = engine.backend.received_commands[3] - assert cmd.gate == projectq_gate - if cmd.gate == Swap: - assert len(cmd.control_qubits) == 1 - assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 2 - else: - assert len(cmd.control_qubits) == 2 - assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 - - -def test_read_qasm2_if_expr(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[2]; -creg c0[1]; -measure q[0] -> c0; -if (c0 == 1) x q[0]; -""" - - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert bits_map == {'c0': [True]} - assert len(engine.backend.received_commands) == 4 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == Allocate - assert engine.backend.received_commands[2].gate == FlushGate() - cmd = engine.backend.received_commands[3] - assert cmd.gate == X - assert len([qubit for qureg in cmd.qubits for qubit in qureg]) == 1 - - engine = deepcopy(eng) - engine.backend.measurement_result = False - qubits_map, bits_map = read_qasm_str(engine, qasm_str) - assert bits_map == {'c0': [False]} - assert len(engine.backend.received_commands) == 3 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == Allocate - assert engine.backend.received_commands[2].gate == FlushGate() - - -def test_read_qasm2_gate_def(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[2]; - -gate my_cu1(lambda) a,b -{ - u1(lambda/2) a; - cx a,b; - u1(-lambda/2) b; - cx a,b; - u1(lambda/2) b; -} -gate empty() a,b -{ -} - -empty q[0],q[1]; -my_cu1(1) q[0],q[1]; -""" - - qubits_map, bits_map = read_qasm_str(eng, qasm_str) - assert not bits_map - assert len(eng.backend.received_commands) == 7 - assert eng.backend.received_commands[0].gate == Allocate - assert eng.backend.received_commands[1].gate == Allocate - - a, b = qubits_map['q'] - - cmd = eng.backend.received_commands[2] - assert cmd.gate == Rz(0.5) - assert [qubit for qureg in cmd.qubits for qubit in qureg] == [a] - - cmd = eng.backend.received_commands[3] - assert cmd.gate == X - assert cmd.control_qubits == [a] - assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] - - cmd = eng.backend.received_commands[4] - assert cmd.gate == Rz(-0.5) - assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] - - cmd = eng.backend.received_commands[5] - assert cmd.gate == X - assert cmd.control_qubits == [a] - assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] - - cmd = eng.backend.received_commands[6] - assert cmd.gate == Rz(0.5) - assert [qubit for qureg in cmd.qubits for qubit in qureg] == [b] - - -def test_read_qasm2_file(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[2]; -creg c0[1]; -""" - - with tempfile.NamedTemporaryFile() as fd: - fd.write((qasm_str + '\n').encode()) - fd.seek(0) - engine = deepcopy(eng) - qubits_map, bits_map = read_qasm_file(engine, fd.name) - assert bits_map == {'c0': [False]} - assert len(engine.backend.received_commands) == 2 - assert engine.backend.received_commands[0].gate == Allocate - assert engine.backend.received_commands[1].gate == Allocate - - -def test_qasm_qft2(eng): - qasm_str = """ -OPENQASM 2.0; -include "qelib1.inc"; -qreg a[2]; -creg b[2]; -measure a -> b; -qreg q[3]; -creg c[3]; -// optional post-rotation for state tomography -gate post q { } -u3(0.3,0.2,0.1) q[0]; -h q[1]; -cx q[1],q[2]; -barrier q; -cx q[0],q[1]; -h q[0]; -measure q[0] -> c[0]; -measure q[1] -> c[1]; -if(c==1) z q[2]; -if(c==2) x q[2]; -if(c==3) y q[2]; -if(c==4) z q[1]; -if(c==5) x q[1]; -if(c==6) y q[1]; -if(c==7) z q[0]; -if(c==8) x q[0]; -post q[2]; -measure q[2] -> c[2]; - """ - - qubits_map, bits_map = read_qasm_str(eng, qasm_str) - assert {'q', 'a'} == set(qubits_map) - assert len(qubits_map['a']) == 2 - assert len(qubits_map['q']) == 3 - assert bits_map == {'b': [True, True], 'c': [True, True, True]} - - assert len([ - cmd for cmd in eng.backend.received_commands if cmd.gate == Allocate - ]) == 5 - assert len([ - cmd for cmd in eng.backend.received_commands - if isinstance(cmd.gate, U3) - ]) == 1 - assert len([cmd for cmd in eng.backend.received_commands - if cmd.gate == X]) == 2 - cmds = [cmd for cmd in eng.backend.received_commands if cmd.gate == Y] - assert len(cmds) == 1 - assert ([qubits_map['q'][1] - ] == [qubit for qureg in cmds[0].qubits for qubit in qureg]) - assert len([cmd for cmd in eng.backend.received_commands - if cmd.gate == Z]) == 0 - assert len([cmd for cmd in eng.backend.received_commands - if cmd.gate == H]) == 2 - cmds = [cmd for cmd in eng.backend.received_commands if cmd.gate == Barrier] - assert len(cmds) == 1 - assert len([qubit for qureg in cmds[0].qubits for qubit in qureg]) == 3 - - -# ============================================================================== -# OpenQASM 3.0 tests (experimental) - - -@pytest.mark.xfail -def test_read_qasm3_empty(eng): - qasm_str = """ -OPENQASM 3.0; -include "stdgates.qasm"; -""" - - qubits_map, bits_map = read_qasm_str(eng, qasm_str) - assert not eng.backend.received_commands diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py new file mode 100644 index 00000000..f0174ddb --- /dev/null +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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. +""" +Helper module to parse expressions +""" + +import math +from numbers import Number +import operator + +from pyparsing import (Literal, Word, Group, Forward, alphas, alphanums, Regex, + CaselessKeyword, Suppress, delimitedList, + pyparsing_common) + +# ============================================================================== + +EXPR_STACK = [] + + +def push_first(toks): + """ + Push first token on top of the stack. + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + EXPR_STACK.append(toks[0]) + + +def push_unary_minus(toks): + """ + Push a unary minus operation on top of the stack if required. + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + if toks[0] == '-': + EXPR_STACK.append('unary -') + + +# ============================================================================== + + +class ExprParser: + """ + Expression parser + + Grammar: + expop :: '^' + multop :: '*' | '/' + addop :: '+' | '-' + integer :: ['+' | '-'] '0'..'9'+ + atom :: PI | E | real | fn '(' expr ')' | '(' expr ')' + factor :: atom [ expop factor ]* + term :: factor [ multop factor ]* + expr :: term [ addop term ]* + """ + def __init__(self): + # pylint: disable = too-many-locals + self.var_dict = dict() + + # use CaselessKeyword for e and pi, to avoid accidentally matching + # functions that start with 'e' or 'pi' (such as 'exp'); Keyword + # and CaselessKeyword only match whole words + e_const = CaselessKeyword("E").addParseAction(lambda: math.e) + pi_const = (CaselessKeyword("PI") + | CaselessKeyword("π")).addParseAction(lambda: math.pi) + # fnumber = Combine(Word("+-"+nums, nums) + + # Optional("." + Optional(Word(nums))) + + # Optional(e + Word("+-"+nums, nums))) + # or use provided pyparsing_common.number, but convert back to str: + # fnumber = ppc.number().addParseAction(lambda t: str(t[0])) + fnumber = Regex(r"[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?") + fnumber = pyparsing_common.number + cname = Word(alphas + "_", alphanums + '_') + int_v = pyparsing_common.integer + + plus, minus, mult, div = map(Literal, "+-*/") + lpar, rpar, lbra, rbra = map(Suppress, "()[]") + addop = plus | minus + multop = mult | div + expop = Literal("^") + + expr = Forward() + expr_list = delimitedList(Group(expr)) + + # add parse action that replaces the function identifier with a (name, + # number of args) tuple + def insert_fn_argcount_tuple(toks): + fn_name = toks.pop(0) + num_args = len(toks[0]) + toks.insert(0, (fn_name, num_args)) + + var_expr = (cname + (lbra + int_v + rbra)[...]).addParseAction( + self._eval_var_expr) + + fn_call = (cname + lpar - Group(expr_list) + + rpar).setParseAction(insert_fn_argcount_tuple) + atom = (addop[...] + + ((fn_call | pi_const | e_const | var_expr | fnumber + | cname).setParseAction(push_first) + | Group(lpar + expr + rpar))).setParseAction(push_unary_minus) + + # by defining exponentiation as "atom [ ^ factor ]..." instead of + # "atom [ ^ atom ]...", we get right-to-left + # exponents, instead of left-to-right that is, 2^3^2 = 2^(3^2), not + # (2^3)^2. + factor = Forward() + factor <<= atom + (expop + factor).setParseAction(push_first)[...] + term = factor + (multop + factor).setParseAction(push_first)[...] + expr <<= term + (addop + term).setParseAction(push_first)[...] + self.bnf = expr + + def parse(self, expr): + """ + Parse an expression. + + Args: + expr (str): Expression to evaluate + """ + return self.bnf.parseString(expr, parseAll=True) + + def set_var_dict(self, var_dict): + """ + Set the internal variable dictionary. + + Args: + var_dict (dict): Dictionary of variables with their corresponding + value for substitution. + """ + self.var_dict = var_dict + + def _eval_var_expr(self, toks): + """ + Evaluate an expression containing a variable. + + Name matching keys in the internal variable dictionary have their + values substituted. + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ + if len(toks) == 1: + return self.var_dict[toks[0]] + + value, index = toks + value = self.var_dict[value] + + if isinstance(value, list): + return value[index] + + if isinstance(value, int): + # Might be faster than (value >> index) & 1 + return int(bool(value & (1 << index))) + + # TODO: Properly handle other types... + return value + + +# map operator symbols to corresponding arithmetic operations +EPSILON = 1e-12 +opn = { + "+": operator.add, + "-": operator.sub, + "*": operator.mul, + "/": operator.truediv, + "^": operator.pow, +} + +fn = { + "sin": math.sin, + "cos": math.cos, + "tan": math.tan, + "exp": math.exp, + "abs": abs, + "trunc": int, + "round": round, + "sgn": lambda a: -1 if a < -EPSILON else 1 if a > EPSILON else 0, + "all": lambda *a: all(a), + "float": float, + "int": int, + "bool": bool, +} + + +def evaluate_stack(stack): + """ + Evaluate a stack of operations. + + Args: + stack (list): Expression stack + + Returns: + Result of evaluating the operation at the top of the stack. + """ + # pylint: disable=invalid-name + + op, num_args = stack.pop(), 0 + if isinstance(op, tuple): + op, num_args = op + + if isinstance(op, Number): + return op + + if op == "unary -": + return -evaluate_stack(stack) + + if op in "+-*/^": + # note: operands are pushed onto the stack in reverse order + op2 = evaluate_stack(stack) + op1 = evaluate_stack(stack) + return opn[op](op1, op2) + + if op in fn: + # note: args are pushed onto the stack in reverse order + args = reversed([evaluate_stack(stack) for _ in range(num_args)]) + return fn[op](*args) + + # try to evaluate as int first, then as float if int fails + try: + return int(op) + except ValueError: + return float(op) + + +_parser = ExprParser() + + +def eval_expr(expr_str, var_dict=None): + """ + Evaluate a mathematical expression. + + Args: + expr_str (str): Expression to evaluate + var_dict (dict): Dictionary of variables with their corresponding + value for substitution. + + Returns: + Result of evaluation. + """ + # pylint: disable = global-statement + global EXPR_STACK + EXPR_STACK = [] + + _parser.set_var_dict(var_dict if var_dict else dict()) + _parser.parse(expr_str) + return evaluate_stack(EXPR_STACK[:]) From a57f77fa3cdb9612a5d07e09b7438b4b510f35c3 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 24 Feb 2021 15:53:04 +0100 Subject: [PATCH 07/42] Fix errors on Travis CI --- projectq/libs/qasm/_parse_qasm_pyparsing.py | 72 +++++++++++---------- projectq/libs/qasm/_pyparsing_expr.py | 22 ++++--- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index a2e88b3f..b1252248 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -19,9 +19,9 @@ from copy import deepcopy import operator as op from pyparsing import (Literal, Word, Group, Or, CharsNotIn, Optional, - nestedExpr, Suppress, removeQuotes, Empty, - cppStyleComment, cStyleComment, dblQuotedString, alphas, - alphanums, pyparsing_common) + OneOrMore, ZeroOrMore, nestedExpr, Suppress, + removeQuotes, Empty, cppStyleComment, cStyleComment, + dblQuotedString, alphas, alphanums, pyparsing_common) from projectq.ops import All, Measure @@ -412,8 +412,8 @@ def __init__(self, s, loc, toks): self.qubits = [QubitProxy(qubit) for qubit in toks[1]] else: param_str = s[loc:s.find(';', loc)] - self.params = param_str[param_str.find('(') + - 1:param_str.rfind(')')].split(',') + self.params = param_str[param_str.find('(') + + 1:param_str.rfind(')')].split(',') self.qubits = [QubitProxy(qubit) for qubit in toks[2]] def eval(self, eng): @@ -567,8 +567,8 @@ class IfOp: logical_bin_op = Or( [greater, greater_equal, less, less_equal, equal, not_equal]) - cond_expr = (CommonTokens.variable_expr.copy() + logical_bin_op + - CharsNotIn('()')) + cond_expr = (CommonTokens.variable_expr.copy() + logical_bin_op + + CharsNotIn('()')) def __init__(self, if_str, loc, toks): """ @@ -763,11 +763,12 @@ def __init__(self): # Variable declarations # e.g. qubit[10] qr, qs / int[5] i, j - variable_decl_const_bits = type_t + Group(cname + (comma + cname)[...]) + variable_decl_const_bits = type_t + Group(cname + + ZeroOrMore(comma + cname)) # e.g. qubit qr[10], qs[2] / int i[5], j[10] - variable_decl_var_bits = var_type + Group(variable_expr + - (comma + variable_expr)[...]) + variable_decl_var_bits = var_type + Group( + variable_expr + ZeroOrMore(comma + variable_expr)) # e.g. int[10] i = 5; variable_decl_assign = type_t + cname + equal_sign + Group(expr) @@ -781,11 +782,11 @@ def __init__(self): # ---------------------------------------------------------------------- # Gate operations - gate_op_no_param = cname + Group(variable_expr + - (comma + variable_expr)[...]) - gate_op_w_param = cname + Group( - nestedExpr(ignoreExpr=comma)) + Group(variable_expr + - (comma + variable_expr)[...]) + gate_op_no_param = cname + Group(variable_expr + + ZeroOrMore(comma + variable_expr)) + gate_op_w_param = cname + Group(nestedExpr( + ignoreExpr=comma)) + Group(variable_expr + + ZeroOrMore(comma + variable_expr)) # ---------------------------------- # Measure gate operations @@ -810,7 +811,7 @@ def __init__(self): # NB: not exactly 100% OpenQASM 3.0 conformant... if_expr_qasm3 = (Literal("if") + nestedExpr(ignoreExpr=comma) + - (lbrace + Group(gate_op + end)[1, ...] + rbrace)) + (lbrace + OneOrMore(Group(gate_op + end)) + rbrace)) if_expr = (if_expr_qasm2 ^ if_expr_qasm3).addParseAction(IfOp) assign_op = (cname + equal_sign + Group(expr)).addParseAction(AssignOp) @@ -829,30 +830,31 @@ def __init__(self): param_decl = Group(param_decl_qasm2 ^ param_decl_qasm3) - qargs_list = Group(cname + (comma + cname)[...]) + qargs_list = Group(cname + ZeroOrMore(comma + cname)) - gate_def_no_args = (lpar + rpar)[...] + Group(Empty()) + qargs_list - gate_def_w_args = (lpar + Group(param_decl + - (comma + param_decl)[...]) + rpar + - qargs_list) + gate_def_no_args = ZeroOrMore(lpar + rpar) + Group( + Empty()) + qargs_list + gate_def_w_args = (lpar + + Group(param_decl + ZeroOrMore(comma + param_decl)) + + rpar + qargs_list) gate_def_expr = (Literal("gate") + cname + - (gate_def_no_args ^ gate_def_w_args) + lbrace + Group( - (gate_op + end)[...]) + - rbrace).addParseAction(GateDefOp) + (gate_def_no_args ^ gate_def_w_args) + lbrace + + Group(ZeroOrMore(gate_op + end)) + + rbrace).addParseAction(GateDefOp) # ---------------------------------- # Opaque gate declarations operations opaque_def_expr = (Literal("opaque") + cname + - (gate_def_no_args ^ gate_def_w_args) + - end).addParseAction(OpaqueDefOp) + (gate_def_no_args ^ gate_def_w_args) + + end).addParseAction(OpaqueDefOp) # ---------------------------------------------------------------------- # Control related expressions (OpenQASM 3.0) - # control_var_type = Or([length_t, stretch_t + int_v[...]]) + # control_var_type = Or([length_t, stretch_t + ZeroOrMore(int_v)]) - # lengthof_arg = Word(alphanums + '_+-*/%{}[]')[1, ...] + # lengthof_arg = OneOrMore(Word(alphanums + '_+-*/%{}[]')) # lengthof_op = Literal("lengthof") + lpar + lengthof_arg + rpar # units = Word(alphas) @@ -864,16 +866,16 @@ def __init__(self): # control_variable_decl_statement = Group( # Or([control_variable_decl, control_variable_decl_assign])) - # control_func_arg = Word(alphanums + '_+-*/%')[1, ...] + # control_func_arg = OneOrMore(Word(alphanums + '_+-*/%')) # control_func_op = Literal("rotary") # rotary_op = (control_func_op + lpar + control_func_arg + rpar + lbra # + ((float_v + units) ^ control_func_arg) + rbra + - # variable_expr + (comma + variable_expr)[...]) + # variable_expr + ZeroOrMore(comma + variable_expr)) - # delay_arg = Word(alphanums + '_+-*/%')[1, ...] + # delay_arg = OneOrMore(Word(alphanums + '_+-*/%')) # delay_op = Literal("delay") + lbra + delay_arg + rbra + Optional( - # variable_expr + (comma + variable_expr)[...]) + # variable_expr + ZeroOrMore(comma + variable_expr)) # control_op = Group(Or([rotary_op, delay_op])) @@ -882,8 +884,8 @@ def __init__(self): header = (Suppress("OPENQASM") + (int_v ^ float_v).addParseAction(QASMVersionOp) + end) - include = (Suppress("include") + string_v.addParseAction(IncludeOp) + - end) + include = (Suppress("include") + string_v.addParseAction(IncludeOp) + + end) statement = ( (measure_op + end) @@ -898,7 +900,7 @@ def __init__(self): # | (control_op + end).addParseAction(lambda toks: []) ) - self.parser = header + include[...] + statement[...] + self.parser = header + ZeroOrMore(include) + ZeroOrMore(statement) self.parser.ignore(cppStyleComment) self.parser.ignore(cStyleComment) diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py index f0174ddb..9525af69 100644 --- a/projectq/libs/qasm/_pyparsing_expr.py +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -20,9 +20,9 @@ from numbers import Number import operator -from pyparsing import (Literal, Word, Group, Forward, alphas, alphanums, Regex, - CaselessKeyword, Suppress, delimitedList, - pyparsing_common) +from pyparsing import (Literal, Word, Group, Forward, ZeroOrMore, alphas, + alphanums, Regex, CaselessKeyword, Suppress, + delimitedList, pyparsing_common) # ============================================================================== @@ -103,12 +103,12 @@ def insert_fn_argcount_tuple(toks): num_args = len(toks[0]) toks.insert(0, (fn_name, num_args)) - var_expr = (cname + (lbra + int_v + rbra)[...]).addParseAction( + var_expr = (cname + ZeroOrMore(lbra + int_v + rbra)).addParseAction( self._eval_var_expr) - fn_call = (cname + lpar - Group(expr_list) + - rpar).setParseAction(insert_fn_argcount_tuple) - atom = (addop[...] + + fn_call = (cname + lpar - Group(expr_list) + + rpar).setParseAction(insert_fn_argcount_tuple) + atom = (ZeroOrMore(addop) + ((fn_call | pi_const | e_const | var_expr | fnumber | cname).setParseAction(push_first) | Group(lpar + expr + rpar))).setParseAction(push_unary_minus) @@ -118,9 +118,11 @@ def insert_fn_argcount_tuple(toks): # exponents, instead of left-to-right that is, 2^3^2 = 2^(3^2), not # (2^3)^2. factor = Forward() - factor <<= atom + (expop + factor).setParseAction(push_first)[...] - term = factor + (multop + factor).setParseAction(push_first)[...] - expr <<= term + (addop + term).setParseAction(push_first)[...] + factor <<= atom + ZeroOrMore( + (expop + factor).setParseAction(push_first)) + term = factor + ZeroOrMore( + (multop + factor).setParseAction(push_first)) + expr <<= term + ZeroOrMore((addop + term).setParseAction(push_first)) self.bnf = expr def parse(self, expr): From 623ba96fbfc4ccef3ed976d9aaaa5fd371cebaeb Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 24 Feb 2021 18:01:08 +0100 Subject: [PATCH 08/42] Add tests for Qiskit parsing --- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 projectq/libs/qasm/_parse_qasm_qiskit_test.py diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py new file mode 100644 index 00000000..94e88012 --- /dev/null +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -0,0 +1,229 @@ +# Copyright 2021 +# +# 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 +# +# http://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. + +import pytest +import tempfile + +from projectq.ops import (All, Measure, AllocateQubitGate, XGate, MeasureGate, + HGate, SGate, TGate) +from projectq.cengines import MainEngine, DummyEngine +from projectq.backends import CommandPrinter +from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str + +# ============================================================================== + +_has_qiskit = True +try: + import qiskit +except ImportError: + _has_qiskit = False + +has_qiskit = pytest.mark.skipif(not _has_qiskit, + reason="Qiskit is not installed") + +# ------------------------------------------------------------------------------ + + +@pytest.fixture +def eng(): + return MainEngine(backend=DummyEngine(save_commands=True), engine_list=[]) + + +@pytest.fixture +def dummy_eng(): + dummy = DummyEngine(save_commands=True) + eng = MainEngine(backend=CommandPrinter(accept_input=False, + default_measure=True), + engine_list=[dummy]) + return dummy, eng + + +@pytest.fixture +def iqft_example(): + return ''' +// QFT and measure, version 1 +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[4]; +creg c[4]; +h q; +barrier q; +h q[0]; + +measure q[0] -> c[0]; +if(c==1) u1(pi/2) q[1]; +h q[1]; +measure q[1] -> c[1]; +if(c==1) u1(pi/4) q[2]; +if(c==2) u1(pi/2) q[2]; +if(c==3) u1(pi/2+pi/4) q[2]; +h q[2]; +measure q[2] -> c[2]; +if(c==1) u1(pi/8) q[3]; +if(c==2) u1(pi/4) q[3]; +if(c==3) u1(pi/4+pi/8) q[3]; +if(c==4) u1(pi/2) q[3]; +if(c==5) u1(pi/2+pi/8) q[3]; +if(c==6) u1(pi/2+pi/4) q[3]; +if(c==7) u1(pi/2+pi/4+pi/8) q[3]; +h q[3]; +measure q[3] -> c[3]; +''' + + +# ============================================================================== + + +def filter_gates(dummy, gate_class): + return [ + cmd for cmd in dummy.received_commands + if isinstance(cmd.gate, gate_class) + ] + + +def exclude_gates(dummy, gate_class): + return [ + cmd for cmd in dummy.received_commands + if not isinstance(cmd.gate, gate_class) + ] + + +# ============================================================================== + + +@has_qiskit +def test_read_qasm_allocation(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[1]; +creg c[1]; +qreg q2[3]; +creg c2[2]; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q', 'q2'} == set(qubits_map) + assert len(qubits_map['q']) == 1 + assert len(qubits_map['q2']) == 3 + assert {'c', 'c2'} == set(bits_map) + assert len(bits_map['c']) == 1 + assert len(bits_map['c2']) == 2 + assert all( + isinstance(cmd.gate, AllocateQubitGate) + for cmd in eng.backend.received_commands) + + +@has_qiskit +def test_read_qasm_if_expr_single_cbit(dummy_eng): + dummy, eng = dummy_eng + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; +qreg a[1]; +creg b[1]; +if(b==1) x a; +measure a -> b; +if(b==1) x a; +measure a -> b; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'a'} == set(qubits_map) + assert len(qubits_map['a']) == 1 + assert {'b'} == set(bits_map) + assert len(bits_map['b']) == 1 + assert len(filter_gates(dummy, AllocateQubitGate)) == 1 + assert len(filter_gates(dummy, XGate)) == 1 + assert len(filter_gates(dummy, MeasureGate)) == 2 + + +@has_qiskit +def test_read_qasm_custom_gate(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; + +qreg q[3]; +creg c[3]; +gate cH a,b { +h b; +sdg b; +cx a,b; +h b; +t b; +cx a,b; +t b; +h b; +s b; +x b; +s a; + } +cH q[0],q[1]; +''' + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 3 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 3 + assert len(filter_gates(eng.backend, AllocateQubitGate)) == 3 + assert len(filter_gates(eng.backend, XGate)) == 3 + assert len(filter_gates(eng.backend, HGate)) == 3 + assert len(filter_gates(eng.backend, TGate)) == 2 + assert len(filter_gates(eng.backend, SGate)) == 2 + # + 1 DaggeredGate for sdg + + +@has_qiskit +def test_read_qasm_opaque_gate(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; + +opaque mygate q1, q2, q3; +qreg q[3]; +creg c[3]; + +mygate q[0], q[1], q[2]; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 3 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 3 + assert len(eng.backend.received_commands) == 3 # Only allocate gates + + +@has_qiskit +def test_read_qasm2_str(dummy_eng, iqft_example): + dummy, eng = dummy_eng + qubits_map, bits_map = read_qasm_str(eng, iqft_example) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 4 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 4 + + +@has_qiskit +def test_read_qasm2_file(dummy_eng, iqft_example): + dummy, eng = dummy_eng + + with tempfile.NamedTemporaryFile('w') as fd: + fd.write(iqft_example) + fd.flush() + qubits_map, bits_map = read_qasm_file(eng, fd.name) + + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 4 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 4 From 5dc60dcaf9635e7cd1a3d6934d1a83beaa1a051d Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 24 Feb 2021 18:04:04 +0100 Subject: [PATCH 09/42] Add pyparsing and qiskit as extras_require --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index c5248b57..be43d4bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,10 @@ zip_safe = False packages = find: +pyparsing = pyparsing +qiskit = qiskit +qasm = qiskit + # ============================================================================== [flake8] From 9a6899ab4e2a1433469c112d1fefc24244438447 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 24 Feb 2021 18:31:19 +0100 Subject: [PATCH 10/42] Add tests for PyParsing QASM parser + fix bug --- projectq/libs/qasm/_parse_qasm_pyparsing.py | 3 +- .../libs/qasm/_parse_qasm_pyparsing_test.py | 255 ++++++++++++++++++ 2 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 projectq/libs/qasm/_parse_qasm_pyparsing_test.py diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index b1252248..2a4ea67f 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -406,7 +406,8 @@ def __init__(self, s, loc, toks): Args: toks (pyparsing.Tokens): Pyparsing tokens """ - self.name = toks[0].lower() + # self.name = toks[0].lower() + self.name = toks[0] if len(toks) == 2: self.params = [] self.qubits = [QubitProxy(qubit) for qubit in toks[1]] diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py new file mode 100644 index 00000000..58a33e59 --- /dev/null +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -0,0 +1,255 @@ +# Copyright 2021 +# +# 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 +# +# http://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. + +import pytest +import tempfile + +from projectq.ops import (All, Measure, AllocateQubitGate, XGate, MeasureGate, + HGate, SGate, TGate) +from projectq.cengines import MainEngine, DummyEngine +from projectq.backends import CommandPrinter +from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str, _CUSTOM_GATES + +# ============================================================================== + +_has_pyparsing = True +try: + import pyparsing +except ImportError: + _has_pyparsing = False + +has_pyparsing = pytest.mark.skipif(not _has_pyparsing, + reason="Qiskit is not installed") + +# ------------------------------------------------------------------------------ + + +@pytest.fixture +def eng(): + return MainEngine(backend=DummyEngine(save_commands=True), engine_list=[]) + + +@pytest.fixture +def dummy_eng(): + dummy = DummyEngine(save_commands=True) + eng = MainEngine(backend=CommandPrinter(accept_input=False, + default_measure=True), + engine_list=[dummy]) + return dummy, eng + + +@pytest.fixture +def iqft_example(): + return ''' +// QFT and measure, version 1 +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[4]; +creg c[4]; +h q; +barrier q; +h q[0]; + +measure q[0] -> c[0]; +if(c==1) u1(pi/2) q[1]; +h q[1]; +measure q[1] -> c[1]; +if(c==1) u1(pi/4) q[2]; +if(c==2) u1(pi/2) q[2]; +if(c==3) u1(pi/2+pi/4) q[2]; +h q[2]; +measure q[2] -> c[2]; +if(c==1) u1(pi/8) q[3]; +if(c==2) u1(pi/4) q[3]; +if(c==3) u1(pi/4+pi/8) q[3]; +if(c==4) u1(pi/2) q[3]; +if(c==5) u1(pi/2+pi/8) q[3]; +if(c==6) u1(pi/2+pi/4) q[3]; +if(c==7) u1(pi/2+pi/4+pi/8) q[3]; +h q[3]; +measure q[3] -> c[3]; +''' + + +# ============================================================================== + + +def filter_gates(dummy, gate_class): + return [ + cmd for cmd in dummy.received_commands + if isinstance(cmd.gate, gate_class) + ] + + +def exclude_gates(dummy, gate_class): + return [ + cmd for cmd in dummy.received_commands + if not isinstance(cmd.gate, gate_class) + ] + + +# ============================================================================== + + +@has_pyparsing +def test_read_qasm_allocation(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[1]; +creg c[1]; +qreg q2[3]; +creg c2[2]; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q', 'q2'} == set(qubits_map) + assert len(qubits_map['q']) == 1 + assert len(qubits_map['q2']) == 3 + assert {'c', 'c2'} == set(bits_map) + assert len(bits_map['c']) == 1 + assert len(bits_map['c2']) == 2 + assert all( + isinstance(cmd.gate, AllocateQubitGate) + for cmd in eng.backend.received_commands) + + +@has_pyparsing +def test_read_qasm_if_expr_single_cbit(dummy_eng): + dummy, eng = dummy_eng + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; +qreg a[1]; +creg b; +if(b==1) x a; +measure a[0] -> b; +if(b==1) x a; +measure a -> b; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'a'} == set(qubits_map) + assert len(qubits_map['a']) == 1 + assert {'b'} == set(bits_map) + assert len(bits_map['b']) == 1 + assert len(filter_gates(dummy, AllocateQubitGate)) == 1 + assert len(filter_gates(dummy, XGate)) == 1 + assert len(filter_gates(dummy, MeasureGate)) == 2 + + +@has_pyparsing +def test_read_qasm_custom_gate(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; + +qreg q[3]; +creg c[3]; +gate empty a, b {} +gate cHH a,b { +h b; +sdg b; +cx a,b; +h b; +t b; +cx a,b; +t b; +h b; +s b; +x b; +s a; + } +cHH q[0],q[1]; +''' + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 3 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 3 + assert len(filter_gates(eng.backend, AllocateQubitGate)) == 3 + assert len(filter_gates(eng.backend, XGate)) == 3 + assert len(filter_gates(eng.backend, HGate)) == 3 + assert len(filter_gates(eng.backend, TGate)) == 2 + assert len(filter_gates(eng.backend, SGate)) == 2 + # + 1 DaggeredGate for sdg + + +@has_pyparsing +def test_read_qasm_custom_gate_with_param(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; + +qreg q[3]; +creg c[3]; +gate mygate(alpha) a,b { +rx(alpha) a; +x b; + } +mygate(1.2) q[0],q[1]; +''' + + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 3 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 3 + assert len(filter_gates(eng.backend, AllocateQubitGate)) == 3 + assert len(exclude_gates(eng.backend, AllocateQubitGate)) == 2 + + +@has_pyparsing +def test_read_qasm_opaque_gate(eng): + qasm_str = ''' +OPENQASM 2.0; +include "qelib1.inc"; + +opaque mygate q1, q2, q3; +qreg q[3]; +creg c[3]; + +mygate q[0], q[1], q[2]; +''' + qubits_map, bits_map = read_qasm_str(eng, qasm_str) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 3 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 3 + assert len(filter_gates(eng.backend, AllocateQubitGate)) == 3 + assert len(exclude_gates(eng.backend, AllocateQubitGate)) == 1 + + +@has_pyparsing +def test_read_qasm2_str(dummy_eng, iqft_example): + dummy, eng = dummy_eng + qubits_map, bits_map = read_qasm_str(eng, iqft_example) + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 4 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 4 + + +@has_pyparsing +def test_read_qasm2_file(dummy_eng, iqft_example): + dummy, eng = dummy_eng + + with tempfile.NamedTemporaryFile('w') as fd: + fd.write(iqft_example) + fd.flush() + qubits_map, bits_map = read_qasm_file(eng, fd.name) + + assert {'q'} == set(qubits_map) + assert len(qubits_map['q']) == 4 + assert {'c'} == set(bits_map) + assert len(bits_map['c']) == 4 From 3bd2d8922765127091442830db4bbe727008276c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 24 Feb 2021 18:53:07 +0100 Subject: [PATCH 11/42] Added last tests to cover 100% of the new code --- projectq/libs/qasm/_pyparsing_expr.py | 3 +- projectq/libs/qasm/_pyparsing_expr_test.py | 44 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 projectq/libs/qasm/_pyparsing_expr_test.py diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py index 9525af69..73fcfa95 100644 --- a/projectq/libs/qasm/_pyparsing_expr.py +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -168,7 +168,7 @@ def _eval_var_expr(self, toks): return int(bool(value & (1 << index))) # TODO: Properly handle other types... - return value + return value # pragma: no cover # map operator symbols to corresponding arithmetic operations @@ -230,7 +230,6 @@ def evaluate_stack(stack): args = reversed([evaluate_stack(stack) for _ in range(num_args)]) return fn[op](*args) - # try to evaluate as int first, then as float if int fails try: return int(op) except ValueError: diff --git a/projectq/libs/qasm/_pyparsing_expr_test.py b/projectq/libs/qasm/_pyparsing_expr_test.py new file mode 100644 index 00000000..55bd2441 --- /dev/null +++ b/projectq/libs/qasm/_pyparsing_expr_test.py @@ -0,0 +1,44 @@ +# Copyright 2021 +# +# 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 +# +# http://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. + +import pytest +import math + +from ._pyparsing_expr import eval_expr + +# ============================================================================== + + +def test_eval(): + assert eval_expr('1 + 2') == 3 + assert eval_expr('1 + 2^3') == 9 + assert eval_expr('(2.2 + 1) - (2*4 + -1)') == pytest.approx((2.2 + 1) + - (2 * 4 + -1)) + assert eval_expr('-1 + PI') == pytest.approx(-1 + math.pi) + assert eval_expr('-1 + E') == pytest.approx(-1 + math.e) + assert eval_expr('-1 + 2') == 1 + assert eval_expr('a', {'a': '2'}) == 2 + assert eval_expr('a', {'a': '2.2'}) == 2.2 + assert eval_expr('-1 + a', {'a': 2}) == 1 + assert eval_expr('-1 + a[1]', {'a': [2, 3]}) == 2 + assert eval_expr('-1 + a[1]', {'a': 2}) == 0 + assert eval_expr('-1 + a[1]', {'a': 1}) == -1 + assert eval_expr('sin(1.2)') == pytest.approx(math.sin(1.2)) + assert eval_expr('cos(1.2)') == pytest.approx(math.cos(1.2)) + assert eval_expr('tan(1.2)') == pytest.approx(math.tan(1.2)) + assert eval_expr('exp(1.2)') == pytest.approx(math.exp(1.2)) + assert eval_expr('abs(-1.2)') == pytest.approx(abs(-1.2)) + + +# ============================================================================== From 03bc02f70feb8c54168aa0279fb474d11ed46a76 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:02:59 +0200 Subject: [PATCH 12/42] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08fef0a6..04613826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Python context manager `with flushing(MainEngine()) as eng:` +- Added OpenQASMBackend to output QASM from ProjectQ circuits +- Added projectq.libs.qasm to convert QASM to ProjectQ circuits ### Fixed From b026d4f7cf70ed11d75cb3cf6ce31b13167ef50f Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:15:48 +0200 Subject: [PATCH 13/42] Fix almost all issues raised by linters --- projectq/backends/_qasm.py | 136 +++++++------ projectq/backends/_qasm_test.py | 168 +++++++++++----- projectq/libs/qasm/__init__.py | 14 +- projectq/libs/qasm/_parse_qasm_pyparsing.py | 183 +++++++++--------- .../libs/qasm/_parse_qasm_pyparsing_test.py | 27 +-- projectq/libs/qasm/_parse_qasm_qiskit.py | 18 +- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 27 +-- projectq/libs/qasm/_pyparsing_expr.py | 44 +++-- projectq/libs/qasm/_pyparsing_expr_test.py | 4 +- projectq/libs/qasm/_qiskit_conv.py | 6 +- projectq/libs/qasm/_utils.py | 5 +- projectq/libs/qasm/_utils_test.py | 52 +++-- projectq/ops/_basics.py | 48 ++--- projectq/ops/_basics_test.py | 49 ++--- projectq/ops/_gates_test.py | 20 +- 15 files changed, 436 insertions(+), 365 deletions(-) diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py index 73c3abf8..fa99a4f3 100644 --- a/projectq/backends/_qasm.py +++ b/projectq/backends/_qasm.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +18,28 @@ from projectq.cengines import BasicEngine from projectq.meta import get_control_count -from projectq.ops import (X, NOT, Y, Z, T, Tdag, S, Sdag, H, Ph, R, Rx, Ry, Rz, - Swap, Measure, Allocate, Deallocate, Barrier, - FlushGate) +from projectq.ops import ( + X, + NOT, + Y, + Z, + T, + Tdag, + S, + Sdag, + H, + Ph, + R, + Rx, + Ry, + Rz, + Swap, + Measure, + Allocate, + Deallocate, + Barrier, + FlushGate, +) # ============================================================================== @@ -29,12 +49,15 @@ class OpenQASMBackend(BasicEngine): Engine to convert ProjectQ commands to OpenQASM format (either string or file) """ - def __init__(self, - collate=True, - collate_callback=None, - qubit_callback=lambda qubit_id: 'q{}'.format(qubit_id), - bit_callback=lambda qubit_id: 'c{}'.format(qubit_id), - qubit_id_mapping_redux=True): + + def __init__( + self, + collate=True, + collate_callback=None, + qubit_callback=lambda qubit_id: 'q{}'.format(qubit_id), + bit_callback=lambda qubit_id: 'c{}'.format(qubit_id), + qubit_id_mapping_redux=True, + ): """ Initialize an OpenQASMBackend object. @@ -108,10 +131,13 @@ def is_available(self, cmd): elif n_controls == 1: if gate in (H, X, NOT, Y, Z): is_available = True - if isinstance(gate, ( + if isinstance( + gate, + ( R, Rz, - )): + ), + ): is_available = True elif n_controls == 2: if gate in (X, NOT): @@ -170,7 +196,7 @@ def _format_angle(angle): NOT: 'cx', Y: 'cy', Z: 'cz', - Swap: 'cswap' + Swap: 'cswap', } _gates_func = { Barrier: 'barrier', @@ -188,7 +214,7 @@ def _format_angle(angle): NOT: 'x', Y: 'y', Z: 'z', - Swap: 'swap' + Swap: 'swap', } if gate == Allocate: @@ -226,69 +252,65 @@ def _format_angle(angle): assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 qb_id = cmd.qubits[0][0].id - self._output.append('{} = measure {};'.format( - self._creg_dict[qb_id], self._qreg_dict[qb_id])) + self._output.append('{} = measure {};'.format(self._creg_dict[qb_id], self._qreg_dict[qb_id])) elif n_controls == 2: - targets = [ - self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg - ] + targets = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] controls = [self._qreg_dict[qb.id] for qb in cmd.control_qubits] try: - self._output.append('{} {};'.format( - _ccontrolled_gates_func[gate], - ','.join(controls + targets))) + self._output.append('{} {};'.format(_ccontrolled_gates_func[gate], ','.join(controls + targets))) except KeyError: - raise RuntimeError( - 'Unable to perform {} gate with n=2 control qubits'.format( - gate)) + raise RuntimeError('Unable to perform {} gate with n=2 control qubits'.format(gate)) elif n_controls == 1: - target_qureg = [ - self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg - ] + target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] try: if isinstance(gate, Ph): - self._output.append('{}{} {},{};'.format( - _controlled_gates_func[type(gate)], - _format_angle(-gate.angle / 2.), - self._qreg_dict[cmd.control_qubits[0].id], - target_qureg[0])) - elif isinstance(gate, ( + self._output.append( + '{}{} {},{};'.format( + _controlled_gates_func[type(gate)], + _format_angle(-gate.angle / 2.0), + self._qreg_dict[cmd.control_qubits[0].id], + target_qureg[0], + ) + ) + elif isinstance( + gate, + ( R, Rz, - )): - self._output.append('{}{} {},{};'.format( - _controlled_gates_func[type(gate)], - _format_angle(gate.angle), - self._qreg_dict[cmd.control_qubits[0].id], - target_qureg[0])) + ), + ): + self._output.append( + '{}{} {},{};'.format( + _controlled_gates_func[type(gate)], + _format_angle(gate.angle), + self._qreg_dict[cmd.control_qubits[0].id], + target_qureg[0], + ) + ) else: - self._output.append('{} {},{};'.format( - _controlled_gates_func[gate], - self._qreg_dict[cmd.control_qubits[0].id], - *target_qureg)) + self._output.append( + '{} {},{};'.format( + _controlled_gates_func[gate], self._qreg_dict[cmd.control_qubits[0].id], *target_qureg + ) + ) except KeyError: - raise RuntimeError( - 'Unable to perform {} gate with n=1 control qubits'.format( - gate)) + raise RuntimeError('Unable to perform {} gate with n=1 control qubits'.format(gate)) else: - target_qureg = [ - self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg - ] + target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] if isinstance(gate, Ph): - self._output.append('{}{} {};'.format( - _gates_func[type(gate)], _format_angle(-gate.angle / 2.), - target_qureg[0])) + self._output.append( + '{}{} {};'.format(_gates_func[type(gate)], _format_angle(-gate.angle / 2.0), target_qureg[0]) + ) elif isinstance(gate, (R, Rx, Ry, Rz)): - self._output.append('{}{} {};'.format(_gates_func[type(gate)], - _format_angle(gate.angle), - target_qureg[0])) + self._output.append( + '{}{} {};'.format(_gates_func[type(gate)], _format_angle(gate.angle), target_qureg[0]) + ) else: - self._output.append('{} {};'.format(_gates_func[gate], - *target_qureg)) + self._output.append('{} {};'.format(_gates_func[gate], *target_qureg)) def _insert_openqasm_header(self): self._output.append('OPENQASM 3;') diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index 190b607d..251ba27c 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +19,29 @@ import re from projectq.cengines import MainEngine, DummyEngine from projectq.meta import Control -from projectq.ops import (X, NOT, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, - Ry, Rz, Swap, Allocate, Deallocate, Measure, Barrier, - Entangle, Command, All) +from projectq.ops import ( + X, + NOT, + Y, + Z, + T, + Tdagger, + S, + Sdagger, + H, + Ph, + R, + Rx, + Ry, + Rz, + Allocate, + Deallocate, + Measure, + Barrier, + Entangle, + Command, + All, +) from ._qasm import OpenQASMBackend # ============================================================================== @@ -52,7 +73,7 @@ def test_qasm_allocate_deallocate(qubit_id_redux): assert re.search(r'qubit\s+q0', qasm) assert re.search(r'bit\s+c0', qasm) - qureg = eng.allocate_qureg(5) + qureg = eng.allocate_qureg(5) # noqa: F841 eng.flush() assert len(backend._qreg_dict) == 6 @@ -77,7 +98,7 @@ def test_qasm_allocate_deallocate(qubit_id_redux): assert backend._reg_index == 6 assert not backend._available_indices - qubit = eng.allocate_qubit() + qubit = eng.allocate_qubit() # noqa: F841 eng.flush() if qubit_id_redux: @@ -92,61 +113,96 @@ def test_qasm_allocate_deallocate(qubit_id_redux): assert not backend._available_indices -@pytest.mark.parametrize("gate, is_available", - [(X, True), (Y, True), (Z, True), (T, True), - (Tdagger, True), (S, True), (Sdagger, True), - (Allocate, True), (Deallocate, True), (Measure, True), - (NOT, True), (Rx(0.5), True), (Ry(0.5), True), - (Rz(0.5), True), (R(0.5), True), (Ph(0.5), True), - (Barrier, True), (Entangle, False)], - ids=lambda l: str(l)) +@pytest.mark.parametrize( + "gate, is_available", + [ + (X, True), + (Y, True), + (Z, True), + (T, True), + (Tdagger, True), + (S, True), + (Sdagger, True), + (Allocate, True), + (Deallocate, True), + (Measure, True), + (NOT, True), + (Rx(0.5), True), + (Ry(0.5), True), + (Rz(0.5), True), + (R(0.5), True), + (Ph(0.5), True), + (Barrier, True), + (Entangle, False), + ], + ids=lambda l: str(l), +) def test_qasm_is_available(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) qubit1 = eng.allocate_qubit() - cmd = Command(eng, gate, (qubit1, )) + cmd = Command(eng, gate, (qubit1,)) eng.is_available(cmd) == is_available eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) qubit1 = eng.allocate_qubit() - cmd = Command(eng, gate, (qubit1, )) + cmd = Command(eng, gate, (qubit1,)) eng.is_available(cmd) == is_available -@pytest.mark.parametrize("gate, is_available", - [(H, True), (X, True), (NOT, True), (Y, True), - (Z, True), (Rz(0.5), True), (R(0.5), True), - (Rx(0.5), False), (Ry(0.5), False)], - ids=lambda l: str(l)) +@pytest.mark.parametrize( + "gate, is_available", + [ + (H, True), + (X, True), + (NOT, True), + (Y, True), + (Z, True), + (Rz(0.5), True), + (R(0.5), True), + (Rx(0.5), False), + (Ry(0.5), False), + ], + ids=lambda l: str(l), +) def test_qasm_is_available_1control(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) - cmd = Command(eng, gate, (qubit1, ), controls=qureg) + cmd = Command(eng, gate, (qubit1,), controls=qureg) assert eng.is_available(cmd) == is_available eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) - cmd = Command(eng, gate, (qubit1, ), controls=qureg) + cmd = Command(eng, gate, (qubit1,), controls=qureg) assert eng.is_available(cmd) == is_available -@pytest.mark.parametrize("gate, is_available", - [(X, True), (NOT, True), (Y, False), (Z, False), - (Rz(0.5), False), (R(0.5), False), (Rx(0.5), False), - (Ry(0.5), False)], - ids=lambda l: str(l)) +@pytest.mark.parametrize( + "gate, is_available", + [ + (X, True), + (NOT, True), + (Y, False), + (Z, False), + (Rz(0.5), False), + (R(0.5), False), + (Rx(0.5), False), + (Ry(0.5), False), + ], + ids=lambda l: str(l), +) def test_qasm_is_available_2control(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(2) - cmd = Command(eng, gate, (qubit1, ), controls=qureg) + cmd = Command(eng, gate, (qubit1,), controls=qureg) assert eng.is_available(cmd) == is_available eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(2) - cmd = Command(eng, gate, (qubit1, ), controls=qureg) + cmd = Command(eng, gate, (qubit1,), controls=qureg) assert eng.is_available(cmd) == is_available @@ -174,9 +230,23 @@ def test_qasm_test_qasm_single_qubit_gates(): qasm = eng.backend.qasm # Note: ignoring header and footer for comparison assert qasm[2:-1] == [ - 'qubit q0;', 'bit c0;', 'h q0;', 's q0;', 't q0;', 'sdg q0;', 'tdg q0;', - 'x q0;', 'y q0;', 'z q0;', 'u1(0.5) q0;', 'rx(0.5) q0;', 'ry(0.5) q0;', - 'rz(0.5) q0;', 'u1(-0.25) q0;', 'x q0;', 'c0 = measure q0;' + 'qubit q0;', + 'bit c0;', + 'h q0;', + 's q0;', + 't q0;', + 'sdg q0;', + 'tdg q0;', + 'x q0;', + 'y q0;', + 'z q0;', + 'u1(0.5) q0;', + 'rx(0.5) q0;', + 'ry(0.5) q0;', + 'rz(0.5) q0;', + 'u1(-0.25) q0;', + 'x q0;', + 'c0 = measure q0;', ] @@ -200,10 +270,20 @@ def test_qasm_test_qasm_single_qubit_gates_control(): qasm = eng.backend.qasm # Note: ignoring header and footer for comparison assert qasm[2:-1] == [ - 'qubit q0;', 'bit c0;', 'qubit q1;', 'bit c1;', 'ch q1,q0;', - 'cx q1,q0;', 'cy q1,q0;', 'cz q1,q0;', 'cx q1,q0;', 'cu1(0.5) q1,q0;', - 'crz(0.5) q1,q0;', 'cu1(-0.25) q1,q0;', 'c0 = measure q0;', - 'c1 = measure q1;' + 'qubit q0;', + 'bit c0;', + 'qubit q1;', + 'bit c1;', + 'ch q1,q0;', + 'cx q1,q0;', + 'cy q1,q0;', + 'cz q1,q0;', + 'cx q1,q0;', + 'cu1(0.5) q1,q0;', + 'crz(0.5) q1,q0;', + 'cu1(-0.25) q1,q0;', + 'c0 = measure q0;', + 'c1 = measure q1;', ] # Also test invalid gates with 1 control qubits @@ -249,10 +329,9 @@ def test_qasm_no_collate(): def _process(output): qasm_list.append(output) - eng = MainEngine(backend=OpenQASMBackend(collate=False, - collate_callback=_process, - qubit_id_mapping_redux=False), - engine_list=[]) + eng = MainEngine( + backend=OpenQASMBackend(collate=False, collate_callback=_process, qubit_id_mapping_redux=False), engine_list=[] + ) qubit = eng.allocate_qubit() ctrls = eng.allocate_qureg(2) @@ -303,15 +382,10 @@ def _qubit(index): def _bit(index): return 'classical_bit_{}'.format(index) - eng = MainEngine(backend=OpenQASMBackend(qubit_callback=_qubit, - bit_callback=_bit), - engine_list=[]) + eng = MainEngine(backend=OpenQASMBackend(qubit_callback=_qubit, bit_callback=_bit), engine_list=[]) qubit = eng.allocate_qubit() Measure | qubit qasm = eng.backend.qasm - assert qasm[2:] == [ - 'qubit qubit_0;', 'bit classical_bit_0;', - 'classical_bit_0 = measure qubit_0;' - ] + assert qasm[2:] == ['qubit qubit_0;', 'bit classical_bit_0;', 'classical_bit_0 = measure qubit_0;'] diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index 016e5998..3e658124 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +23,14 @@ from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str except ImportError as e: import warnings - err = ('Unable to import either qiskit or pyparsing\n' - 'Please install either of them (e.g. using the ' - 'command python -m pip install qiskit') - warnings.warn(err + '\n' - 'The provided read_qasm_* functions will systematically' - 'raise a RuntimeError') + err = ( + 'Unable to import either qiskit or pyparsing\n' + 'Please install either of them (e.g. using the ' + 'command python -m pip install qiskit' + ) + + warnings.warn(err + '\n' 'The provided read_qasm_* functions will systematically' 'raise a RuntimeError') def read_qasm_file(eng, filename): # pylint: disable=unused-argument diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index 2a4ea67f..459b8e40 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -18,10 +18,26 @@ from copy import deepcopy import operator as op -from pyparsing import (Literal, Word, Group, Or, CharsNotIn, Optional, - OneOrMore, ZeroOrMore, nestedExpr, Suppress, - removeQuotes, Empty, cppStyleComment, cStyleComment, - dblQuotedString, alphas, alphanums, pyparsing_common) +from pyparsing import ( + Literal, + Word, + Group, + Or, + CharsNotIn, + Optional, + OneOrMore, + ZeroOrMore, + nestedExpr, + Suppress, + removeQuotes, + Empty, + cppStyleComment, + cStyleComment, + dblQuotedString, + alphas, + alphanums, + pyparsing_common, +) from projectq.ops import All, Measure @@ -41,6 +57,7 @@ class CommonTokens: """ Some general tokens """ + # pylint: disable = too-few-public-methods int_v = pyparsing_common.signed_integer @@ -63,6 +80,7 @@ class QASMVersionOp: """ OpenQASM version. """ + def __init__(self, toks): self.version = toks[0] @@ -86,6 +104,7 @@ class IncludeOp: """ Include file operation. """ + def __init__(self, toks): """ Constructor @@ -105,8 +124,7 @@ def eval(self, _): if self.fname in 'qelib1.inc, stdlib.inc': pass else: # pragma: nocover - raise RuntimeError('Invalid cannot read: {}! (unsupported)'.format( - self.fname)) + raise RuntimeError('Invalid cannot read: {}! (unsupported)'.format(self.fname)) def __repr__(self): # pragma: nocover """ @@ -122,6 +140,7 @@ class QubitProxy: """ Qubit access proxy class """ + def __init__(self, toks): """ Constructor @@ -186,12 +205,9 @@ def __repr__(self): # pragma: nocover Mainly for debugging """ if self.init: - return "{}({}, {}, {}) = {}".format(self.__class__.__name__, - self.type_t, self.nbits, - self.name, self.init) + return "{}({}, {}, {}) = {}".format(self.__class__.__name__, self.type_t, self.nbits, self.name, self.init) - return "{}({}, {}, {})".format(self.__class__.__name__, self.type_t, - self.nbits, self.name) + return "{}({}, {}, {})".format(self.__class__.__name__, self.type_t, self.nbits, self.name) # ------------------------------------------------------------------------------ @@ -242,8 +258,7 @@ def eval(self, _): init = parse_expr(self.init) # The followings are OpenQASM 3.0 - if self.type_t in ('const', 'float', 'fixed', - 'angle'): # pragma: nocover + if self.type_t in ('const', 'float', 'fixed', 'angle'): # pragma: nocover _BITS_VARS[self.name] = float(init) elif self.type_t in ('int', 'uint'): # pragma: nocover _BITS_VARS[self.name] = int(init) @@ -264,6 +279,7 @@ class GateDefOp: """ Operation representing a gate definition. """ + def __init__(self, toks): """ Constructor @@ -291,15 +307,15 @@ def __repr__(self): # pragma: nocover """ Mainly for debugging """ - return "GateDefOp({}, {}, {})\n\t{}".format(self.name, self.params, - self.qparams, self.body) + return "GateDefOp({}, {}, {})\n\t{}".format(self.name, self.params, self.qparams, self.body) # ============================================================================== class MeasureOp: - """ Measurement operations (OpenQASM 2.0 & 3.0) """ + """Measurement operations (OpenQASM 2.0 & 3.0)""" + def __init__(self, toks): """ Constructor @@ -362,6 +378,7 @@ class OpaqueDefOp: """ Opaque gate definition operation. """ + def __init__(self, toks): """ Constructor @@ -399,6 +416,7 @@ class GateOp: """ Gate applied to qubits operation. """ + def __init__(self, s, loc, toks): """ Constructor @@ -412,9 +430,8 @@ def __init__(self, s, loc, toks): self.params = [] self.qubits = [QubitProxy(qubit) for qubit in toks[1]] else: - param_str = s[loc:s.find(';', loc)] - self.params = param_str[param_str.find('(') - + 1:param_str.rfind(')')].split(',') + param_str = s[loc : s.find(';', loc)] # noqa: E203 + self.params = param_str[param_str.find('(') + 1 : param_str.rfind(')')].split(',') # noqa: E203 self.qubits = [QubitProxy(qubit) for qubit in toks[2]] def eval(self, eng): @@ -426,8 +443,7 @@ def eval(self, eng): """ if self.name in gates_conv_table: if self.params: - gate = gates_conv_table[self.name]( - *[parse_expr(p) for p in self.params]) + gate = gates_conv_table[self.name](*[parse_expr(p) for p in self.params]) else: gate = gates_conv_table[self.name] @@ -455,14 +471,8 @@ def eval(self, eng): assert gate_params assert len(self.params) == len(gate_params) - params_map = { - p_param: p_var - for p_var, p_param in zip(self.params, gate_params) - } - qparams_map = { - q_param: q_var - for q_var, q_param in zip(self.qubits, gate_qparams) - } + params_map = {p_param: p_var for p_var, p_param in zip(self.params, gate_params)} + qparams_map = {q_param: q_var for q_var, q_param in zip(self.qubits, gate_qparams)} # NB: this is a hack... # Will probably not work for multiple expansions of @@ -479,8 +489,7 @@ def eval(self, eng): _BITS_VARS = bits_vars_bak else: # pragma: nocover if self.params: - gate_str = '{}({}) | {}'.format(self.name, self.params, - self.qubits) + gate_str = '{}({}) | {}'.format(self.name, self.params, self.qubits) else: gate_str = '{} | {}'.format(self.name, self.qubits) raise RuntimeError('Unknown gate: {}'.format(gate_str)) @@ -499,6 +508,7 @@ class AssignOp: # pragma: nocover """ Variable assignment operation (OpenQASM 3.0 only) """ + def __init__(self, toks): """ Constructor @@ -522,8 +532,7 @@ def eval(self, _): value = parse_expr(self.value) _BITS_VARS[self.var] = value else: - raise RuntimeError('The variable {} is not defined!'.format( - self.var)) + raise RuntimeError('The variable {} is not defined!'.format(self.var)) return 0 def __repr__(self): @@ -548,9 +557,8 @@ def _parse_if_conditional(if_str): elif ch == ')': level -= 1 if level == 0: - return if_str[start:start + idx] - raise RuntimeError( - 'Unbalanced parantheses in {}'.format(if_str)) # pragma: nocover + return if_str[start : start + idx] # noqa: E203 + raise RuntimeError('Unbalanced parantheses in {}'.format(if_str)) # pragma: nocover class IfOp: @@ -565,11 +573,9 @@ class IfOp: equal = Literal('==').addParseAction(lambda: op.eq) not_equal = Literal('!=').addParseAction(lambda: op.ne) - logical_bin_op = Or( - [greater, greater_equal, less, less_equal, equal, not_equal]) + logical_bin_op = Or([greater, greater_equal, less, less_equal, equal, not_equal]) - cond_expr = (CommonTokens.variable_expr.copy() + logical_bin_op - + CharsNotIn('()')) + cond_expr = CommonTokens.variable_expr.copy() + logical_bin_op + CharsNotIn('()') def __init__(self, if_str, loc, toks): """ @@ -617,8 +623,7 @@ def eval(self, eng): gate.eval(eng) def __repr__(self): # pragma: nocover - return "IfExpr({} {} {}) {{ {} }}".format(self.bit, self.binary_op, - self.comp_expr, self.body) + return "IfExpr({} {} {}) {{ {} }}".format(self.bit, self.binary_op, self.comp_expr, self.body) # ============================================================================== @@ -652,6 +657,7 @@ def _get_name(name): def _get_nbits(_): return nbits + elif len(toks) == 2: # qubit qa, qb[2], qc[3]; names = toks[1] @@ -664,6 +670,7 @@ def _get_nbits(name): # OpenQASM >= 3.0 only return 1 return name[1] + else: # pragma: nocover # OpenQASM >= 3.0 only # const myvar = 1234; @@ -681,16 +688,12 @@ def _get_nbits(_): var_list = [] for name in names: - if type_t in ('const', 'creg', 'bit', 'uint', 'int', 'fixed', 'float', - 'angle', 'bool'): - var_list.append( - CVarOp(type_t, _get_nbits(name), _get_name(name), body)) + if type_t in ('const', 'creg', 'bit', 'uint', 'int', 'fixed', 'float', 'angle', 'bool'): + var_list.append(CVarOp(type_t, _get_nbits(name), _get_name(name), body)) elif body is None: - var_list.append( - QVarOp(type_t, _get_nbits(name), _get_name(name), body)) + var_list.append(QVarOp(type_t, _get_nbits(name), _get_name(name), body)) else: # pragma: nocover - raise RuntimeError( - 'Initializer for quantum variable is unsupported!') + raise RuntimeError('Initializer for quantum variable is unsupported!') return var_list @@ -712,6 +715,7 @@ class QiskitParser: """ Qiskit parser class """ + def __init__(self): # pylint: disable = too-many-locals # ---------------------------------------------------------------------- @@ -747,12 +751,12 @@ def __init__(self): # Variable type matching # Only match an exact type - var_type = Or( - [qubit_t, bit_t, bool_t, const_t, int_t, uint_t, angle_t, float_t]) + var_type = Or([qubit_t, bit_t, bool_t, const_t, int_t, uint_t, angle_t, float_t]) # Match a type or an array of type (e.g. int vs int[10]) type_t = (var_type + Optional(lbra + int_v + rbra, default=1)) | ( - fixed_t + Group(lbra + int_v + comma + int_v + rbra)) + fixed_t + Group(lbra + int_v + comma + int_v + rbra) + ) # ---------------------------------------------------------------------- # (mathematical) expressions @@ -764,55 +768,47 @@ def __init__(self): # Variable declarations # e.g. qubit[10] qr, qs / int[5] i, j - variable_decl_const_bits = type_t + Group(cname - + ZeroOrMore(comma + cname)) + variable_decl_const_bits = type_t + Group(cname + ZeroOrMore(comma + cname)) # e.g. qubit qr[10], qs[2] / int i[5], j[10] - variable_decl_var_bits = var_type + Group( - variable_expr + ZeroOrMore(comma + variable_expr)) + variable_decl_var_bits = var_type + Group(variable_expr + ZeroOrMore(comma + variable_expr)) # e.g. int[10] i = 5; variable_decl_assign = type_t + cname + equal_sign + Group(expr) # Putting it all together - variable_decl_statement = Or([ - variable_decl_const_bits, variable_decl_var_bits, - variable_decl_assign - ]).addParseAction(create_var_decl) + variable_decl_statement = Or( + [variable_decl_const_bits, variable_decl_var_bits, variable_decl_assign] + ).addParseAction(create_var_decl) # ---------------------------------------------------------------------- # Gate operations - gate_op_no_param = cname + Group(variable_expr - + ZeroOrMore(comma + variable_expr)) - gate_op_w_param = cname + Group(nestedExpr( - ignoreExpr=comma)) + Group(variable_expr - + ZeroOrMore(comma + variable_expr)) + gate_op_no_param = cname + Group(variable_expr + ZeroOrMore(comma + variable_expr)) + gate_op_w_param = ( + cname + Group(nestedExpr(ignoreExpr=comma)) + Group(variable_expr + ZeroOrMore(comma + variable_expr)) + ) # ---------------------------------- # Measure gate operations - measure_op_qasm2 = Literal("measure") + variable_expr + Suppress( - "->") + variable_expr - measure_op_qasm3 = variable_expr + equal_sign + Literal( - "measure") + variable_expr + measure_op_qasm2 = Literal("measure") + variable_expr + Suppress("->") + variable_expr + measure_op_qasm3 = variable_expr + equal_sign + Literal("measure") + variable_expr - measure_op = (measure_op_qasm2 - ^ measure_op_qasm3).addParseAction(MeasureOp) + measure_op = (measure_op_qasm2 ^ measure_op_qasm3).addParseAction(MeasureOp) # Putting it all together - gate_op = Or([gate_op_no_param, gate_op_w_param, - measure_op]).addParseAction(GateOp) + gate_op = Or([gate_op_no_param, gate_op_w_param, measure_op]).addParseAction(GateOp) # ---------------------------------------------------------------------- # If expressions - if_expr_qasm2 = Literal("if") + nestedExpr( - ignoreExpr=comma) + gate_op + end + if_expr_qasm2 = Literal("if") + nestedExpr(ignoreExpr=comma) + gate_op + end # NB: not exactly 100% OpenQASM 3.0 conformant... - if_expr_qasm3 = (Literal("if") + nestedExpr(ignoreExpr=comma) + - (lbrace + OneOrMore(Group(gate_op + end)) + rbrace)) + if_expr_qasm3 = ( + Literal("if") + nestedExpr(ignoreExpr=comma) + (lbrace + OneOrMore(Group(gate_op + end)) + rbrace) + ) if_expr = (if_expr_qasm2 ^ if_expr_qasm3).addParseAction(IfOp) assign_op = (cname + equal_sign + Group(expr)).addParseAction(AssignOp) @@ -833,22 +829,23 @@ def __init__(self): qargs_list = Group(cname + ZeroOrMore(comma + cname)) - gate_def_no_args = ZeroOrMore(lpar + rpar) + Group( - Empty()) + qargs_list - gate_def_w_args = (lpar - + Group(param_decl + ZeroOrMore(comma + param_decl)) - + rpar + qargs_list) - gate_def_expr = (Literal("gate") + cname + - (gate_def_no_args ^ gate_def_w_args) + lbrace - + Group(ZeroOrMore(gate_op + end)) - + rbrace).addParseAction(GateDefOp) + gate_def_no_args = ZeroOrMore(lpar + rpar) + Group(Empty()) + qargs_list + gate_def_w_args = lpar + Group(param_decl + ZeroOrMore(comma + param_decl)) + rpar + qargs_list + gate_def_expr = ( + Literal("gate") + + cname + + (gate_def_no_args ^ gate_def_w_args) + + lbrace + + Group(ZeroOrMore(gate_op + end)) + + rbrace + ).addParseAction(GateDefOp) # ---------------------------------- # Opaque gate declarations operations - opaque_def_expr = (Literal("opaque") + cname + - (gate_def_no_args ^ gate_def_w_args) - + end).addParseAction(OpaqueDefOp) + opaque_def_expr = (Literal("opaque") + cname + (gate_def_no_args ^ gate_def_w_args) + end).addParseAction( + OpaqueDefOp + ) # ---------------------------------------------------------------------- # Control related expressions (OpenQASM 3.0) @@ -882,11 +879,9 @@ def __init__(self): # ---------------------------------------------------------------------- - header = (Suppress("OPENQASM") + - (int_v ^ float_v).addParseAction(QASMVersionOp) + end) + header = Suppress("OPENQASM") + (int_v ^ float_v).addParseAction(QASMVersionOp) + end - include = (Suppress("include") + string_v.addParseAction(IncludeOp) - + end) + include = Suppress("include") + string_v.addParseAction(IncludeOp) + end statement = ( (measure_op + end) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index 58a33e59..f0884401 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +16,7 @@ import pytest import tempfile -from projectq.ops import (All, Measure, AllocateQubitGate, XGate, MeasureGate, - HGate, SGate, TGate) +from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate from projectq.cengines import MainEngine, DummyEngine from projectq.backends import CommandPrinter from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str, _CUSTOM_GATES @@ -25,12 +25,11 @@ _has_pyparsing = True try: - import pyparsing + import pyparsing # noqa: F401 except ImportError: _has_pyparsing = False -has_pyparsing = pytest.mark.skipif(not _has_pyparsing, - reason="Qiskit is not installed") +has_pyparsing = pytest.mark.skipif(not _has_pyparsing, reason="Qiskit is not installed") # ------------------------------------------------------------------------------ @@ -43,9 +42,7 @@ def eng(): @pytest.fixture def dummy_eng(): dummy = DummyEngine(save_commands=True) - eng = MainEngine(backend=CommandPrinter(accept_input=False, - default_measure=True), - engine_list=[dummy]) + eng = MainEngine(backend=CommandPrinter(accept_input=False, default_measure=True), engine_list=[dummy]) return dummy, eng @@ -86,17 +83,11 @@ def iqft_example(): def filter_gates(dummy, gate_class): - return [ - cmd for cmd in dummy.received_commands - if isinstance(cmd.gate, gate_class) - ] + return [cmd for cmd in dummy.received_commands if isinstance(cmd.gate, gate_class)] def exclude_gates(dummy, gate_class): - return [ - cmd for cmd in dummy.received_commands - if not isinstance(cmd.gate, gate_class) - ] + return [cmd for cmd in dummy.received_commands if not isinstance(cmd.gate, gate_class)] # ============================================================================== @@ -119,9 +110,7 @@ def test_read_qasm_allocation(eng): assert {'c', 'c2'} == set(bits_map) assert len(bits_map['c']) == 1 assert len(bits_map['c2']) == 2 - assert all( - isinstance(cmd.gate, AllocateQubitGate) - for cmd in eng.backend.received_commands) + assert all(isinstance(cmd.gate, AllocateQubitGate) for cmd in eng.backend.received_commands) @has_pyparsing diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 23fc7773..e89d781c 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,10 +60,9 @@ def apply_op(eng, gate, qubits, bits, bits_map): for gate_sub, quregs_sub, bits_sub in gate._definition.data: # OpenQASM 2.0 limitation... assert gate.name != 'measure' and not bits_sub - apply_op(eng, gate_sub, [ - gate_args[qubit.register.name][qubit.index] - for qubit in quregs_sub - ], [], bits_map) + apply_op( + eng, gate_sub, [gate_args[qubit.register.name][qubit.index] for qubit in quregs_sub], [], bits_map + ) else: if gate.params: gate_projectq = gates_conv_table[gate.name](*gate.params) @@ -104,18 +104,12 @@ def _convert_qiskit_circuit(eng, circuit): although the latter is still experimental. """ # Create maps between qiskit and ProjectQ for qubits and bits - qubits_map = { - qureg.name: eng.allocate_qureg(qureg.size) - for qureg in circuit.qregs - } + qubits_map = {qureg.name: eng.allocate_qureg(qureg.size) for qureg in circuit.qregs} bits_map = {bit.name: [False] * bit.size for bit in circuit.cregs} # Convert all the gates to ProjectQ commands for gate, quregs, bits in circuit.data: - apply_op( - eng, gate, - [qubits_map[qubit.register.name][qubit.index] - for qubit in quregs], bits, bits_map) + apply_op(eng, gate, [qubits_map[qubit.register.name][qubit.index] for qubit in quregs], bits, bits_map) return qubits_map, bits_map diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index 94e88012..b18a0ffd 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +16,7 @@ import pytest import tempfile -from projectq.ops import (All, Measure, AllocateQubitGate, XGate, MeasureGate, - HGate, SGate, TGate) +from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate from projectq.cengines import MainEngine, DummyEngine from projectq.backends import CommandPrinter from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str @@ -25,12 +25,11 @@ _has_qiskit = True try: - import qiskit + import qiskit # noqa: F401 except ImportError: _has_qiskit = False -has_qiskit = pytest.mark.skipif(not _has_qiskit, - reason="Qiskit is not installed") +has_qiskit = pytest.mark.skipif(not _has_qiskit, reason="Qiskit is not installed") # ------------------------------------------------------------------------------ @@ -43,9 +42,7 @@ def eng(): @pytest.fixture def dummy_eng(): dummy = DummyEngine(save_commands=True) - eng = MainEngine(backend=CommandPrinter(accept_input=False, - default_measure=True), - engine_list=[dummy]) + eng = MainEngine(backend=CommandPrinter(accept_input=False, default_measure=True), engine_list=[dummy]) return dummy, eng @@ -86,17 +83,11 @@ def iqft_example(): def filter_gates(dummy, gate_class): - return [ - cmd for cmd in dummy.received_commands - if isinstance(cmd.gate, gate_class) - ] + return [cmd for cmd in dummy.received_commands if isinstance(cmd.gate, gate_class)] def exclude_gates(dummy, gate_class): - return [ - cmd for cmd in dummy.received_commands - if not isinstance(cmd.gate, gate_class) - ] + return [cmd for cmd in dummy.received_commands if not isinstance(cmd.gate, gate_class)] # ============================================================================== @@ -119,9 +110,7 @@ def test_read_qasm_allocation(eng): assert {'c', 'c2'} == set(bits_map) assert len(bits_map['c']) == 1 assert len(bits_map['c2']) == 2 - assert all( - isinstance(cmd.gate, AllocateQubitGate) - for cmd in eng.backend.received_commands) + assert all(isinstance(cmd.gate, AllocateQubitGate) for cmd in eng.backend.received_commands) @has_qiskit diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py index 73fcfa95..61611f38 100644 --- a/projectq/libs/qasm/_pyparsing_expr.py +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -20,9 +20,20 @@ from numbers import Number import operator -from pyparsing import (Literal, Word, Group, Forward, ZeroOrMore, alphas, - alphanums, Regex, CaselessKeyword, Suppress, - delimitedList, pyparsing_common) +from pyparsing import ( + Literal, + Word, + Group, + Forward, + ZeroOrMore, + alphas, + alphanums, + Regex, + CaselessKeyword, + Suppress, + delimitedList, + pyparsing_common, +) # ============================================================================== @@ -67,6 +78,7 @@ class ExprParser: term :: factor [ multop factor ]* expr :: term [ addop term ]* """ + def __init__(self): # pylint: disable = too-many-locals self.var_dict = dict() @@ -75,8 +87,7 @@ def __init__(self): # functions that start with 'e' or 'pi' (such as 'exp'); Keyword # and CaselessKeyword only match whole words e_const = CaselessKeyword("E").addParseAction(lambda: math.e) - pi_const = (CaselessKeyword("PI") - | CaselessKeyword("π")).addParseAction(lambda: math.pi) + pi_const = (CaselessKeyword("PI") | CaselessKeyword("π")).addParseAction(lambda: math.pi) # fnumber = Combine(Word("+-"+nums, nums) + # Optional("." + Optional(Word(nums))) + # Optional(e + Word("+-"+nums, nums))) @@ -103,25 +114,24 @@ def insert_fn_argcount_tuple(toks): num_args = len(toks[0]) toks.insert(0, (fn_name, num_args)) - var_expr = (cname + ZeroOrMore(lbra + int_v + rbra)).addParseAction( - self._eval_var_expr) + var_expr = (cname + ZeroOrMore(lbra + int_v + rbra)).addParseAction(self._eval_var_expr) - fn_call = (cname + lpar - Group(expr_list) - + rpar).setParseAction(insert_fn_argcount_tuple) - atom = (ZeroOrMore(addop) + - ((fn_call | pi_const | e_const | var_expr | fnumber - | cname).setParseAction(push_first) - | Group(lpar + expr + rpar))).setParseAction(push_unary_minus) + fn_call = (cname + lpar - Group(expr_list) + rpar).setParseAction(insert_fn_argcount_tuple) + atom = ( + ZeroOrMore(addop) + + ( + (fn_call | pi_const | e_const | var_expr | fnumber | cname).setParseAction(push_first) + | Group(lpar + expr + rpar) + ) + ).setParseAction(push_unary_minus) # by defining exponentiation as "atom [ ^ factor ]..." instead of # "atom [ ^ atom ]...", we get right-to-left # exponents, instead of left-to-right that is, 2^3^2 = 2^(3^2), not # (2^3)^2. factor = Forward() - factor <<= atom + ZeroOrMore( - (expop + factor).setParseAction(push_first)) - term = factor + ZeroOrMore( - (multop + factor).setParseAction(push_first)) + factor <<= atom + ZeroOrMore((expop + factor).setParseAction(push_first)) + term = factor + ZeroOrMore((multop + factor).setParseAction(push_first)) expr <<= term + ZeroOrMore((addop + term).setParseAction(push_first)) self.bnf = expr diff --git a/projectq/libs/qasm/_pyparsing_expr_test.py b/projectq/libs/qasm/_pyparsing_expr_test.py index 55bd2441..cd91b4de 100644 --- a/projectq/libs/qasm/_pyparsing_expr_test.py +++ b/projectq/libs/qasm/_pyparsing_expr_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +24,7 @@ def test_eval(): assert eval_expr('1 + 2') == 3 assert eval_expr('1 + 2^3') == 9 - assert eval_expr('(2.2 + 1) - (2*4 + -1)') == pytest.approx((2.2 + 1) - - (2 * 4 + -1)) + assert eval_expr('(2.2 + 1) - (2*4 + -1)') == pytest.approx((2.2 + 1) - (2 * 4 + -1)) assert eval_expr('-1 + PI') == pytest.approx(-1 + math.pi) assert eval_expr('-1 + E') == pytest.approx(-1 + math.e) assert eval_expr('-1 + 2') == 1 diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index e4836ff5..a05755ac 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -1,5 +1,5 @@ -from projectq.ops import (Barrier, H, S, Sdagger, T, Tdagger, X, Y, Z, Rx, Ry, - Rz, H, Swap, Toffoli, C, CNOT, U2, U3) +# -*- coding: utf-8 -*- +from projectq.ops import Barrier, H, S, Sdagger, T, Tdagger, X, Y, Z, Rx, Ry, Rz, Swap, Toffoli, C, CNOT, U2, U3 # ============================================================================== # Conversion map between Qiskit gate names and ProjectQ gates @@ -22,7 +22,6 @@ 'u2': lambda p, l: U2(p, l), 'u3': lambda t, p, l: U3(t, p, l), 'phase': lambda a: Rz(a), - # Controlled gates 'ch': C(H), 'cx': CNOT, @@ -33,7 +32,6 @@ 'cu1': lambda a: C(Rz(a)), 'cu2': lambda p, l: C(U2(p, l)), 'cu3': lambda t, p, l: C(U3(t, p, l)), - # Doubly-controlled gates "ccx": Toffoli, } diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py index 8d4c4fad..566e5656 100644 --- a/projectq/libs/qasm/_utils.py +++ b/projectq/libs/qasm/_utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,8 +58,8 @@ def apply_gate(gate, qubits): # pylint: disable = protected-access,pointless-statement if isinstance(gate, ControlledGate): - ctrls = qubits[:gate._n] - qubits = qubits[gate._n:] + ctrls = qubits[: gate._n] + qubits = qubits[gate._n :] # noqa: E203 if isinstance(gate._gate, SwapGate): assert len(qubits) == 2 gate | (ctrls, qubits[0], qubits[1]) diff --git a/projectq/libs/qasm/_utils_test.py b/projectq/libs/qasm/_utils_test.py index 6eb9ea11..2beb9696 100644 --- a/projectq/libs/qasm/_utils_test.py +++ b/projectq/libs/qasm/_utils_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +18,7 @@ from projectq.types import WeakQubitRef from projectq.cengines import DummyEngine -from projectq.ops import (X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, - U2, U3, Swap, Toffoli, Barrier, C) +from projectq.ops import X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, U2, U3, Swap, Toffoli, Barrier, C from ._utils import apply_gate, OpaqueGate # ============================================================================== @@ -41,23 +41,37 @@ def test_opaque_gate(): @pytest.mark.parametrize( - 'gate, n_qubits', (list( - map(lambda x: - (x, 1), [ - X, Y, Z, S, Sdagger, T, Tdagger, H, Barrier, - Ph(1.12), - Rx(1.12), - Ry(1.12), - Rz(1.12), - R(1.12), - U2(1.12, 1.12), - U3(1.12, 1.12, 1.12), - ])) + list(map(lambda x: - (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) + - list(map(lambda x: - (x, 3), [Toffoli, C(Swap), Barrier])) + - list(map(lambda x: (x, 10), [Barrier]))), - ids=str) + 'gate, n_qubits', + ( + list( + map( + lambda x: (x, 1), + [ + X, + Y, + Z, + S, + Sdagger, + T, + Tdagger, + H, + Barrier, + Ph(1.12), + Rx(1.12), + Ry(1.12), + Rz(1.12), + R(1.12), + U2(1.12, 1.12), + U3(1.12, 1.12, 1.12), + ], + ) + ) + + list(map(lambda x: (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) + + list(map(lambda x: (x, 3), [Toffoli, C(Swap), Barrier])) + + list(map(lambda x: (x, 10), [Barrier])) + ), + ids=str, +) def test_apply_gate(gate, n_qubits): backend = DummyEngine() backend.is_last_engine = True diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index a3fa174b..df6e6bfc 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -66,16 +66,14 @@ class NotInvertible(Exception): def _round_angle(angle, mod_pi): rounded_angle = round(float(angle) % (mod_pi * math.pi), ANGLE_PRECISION) if rounded_angle > mod_pi * math.pi - ANGLE_TOLERANCE: - rounded_angle = 0. + rounded_angle = 0.0 return rounded_angle def _angle_to_str(angle, symbols): if symbols: - return ('{}{}'.format(round(angle / math.pi, 3), - unicodedata.lookup("GREEK SMALL LETTER PI"))) - else: - return "{}".format(angle) + return f"({str(round(angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')})" + return f"({str(angle)})" @@ -373,11 +371,7 @@ def to_string(self, symbols=False): symbols (bool): uses the pi character and round the angle for a more user friendly display if True, full angle written in radian otherwise. """ - if symbols: - angle = f"({str(round(self.angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')})" - else: - angle = f"({str(self.angle)})" - return str(self.__class__.__name__) + angle + return str(self.__class__.__name__) + '(' + _angle_to_str(self.angle, symbols) + ')' def tex_str(self): """ @@ -439,6 +433,7 @@ class U3Gate(BasicGate): with the negated argument. Rotation gates of the same class can be merged by adding the angles. The continuous parameter are modulo 4 * pi. """ + def __init__(self, theta, phi, lamda): """ Initialize a general unitary single-qubit gate. @@ -474,10 +469,9 @@ def to_string(self, symbols=False): more user friendly display if True, full angle written in radian otherwise. """ - return (str(self.__class__.__name__) + '({},{},{})'.format( - _angle_to_str(self.theta, symbols), - _angle_to_str(self.phi, symbols), - _angle_to_str(self.lamda, symbols))) + return str(self.__class__.__name__) + '({},{},{})'.format( + _angle_to_str(self.theta, symbols), _angle_to_str(self.phi, symbols), _angle_to_str(self.lamda, symbols) + ) def tex_str(self): """ @@ -490,9 +484,8 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ return str(self.__class__.__name__) + "$({}\\pi,{}\\pi,{}\\pi)$".format( - round(self.theta / math.pi, 3), - round(self.phi / math.pi, 3), - round(self.lamda / math.pi, 3)) + round(self.theta / math.pi, 3), round(self.phi / math.pi, 3), round(self.lamda / math.pi, 3) + ) def get_inverse(self): """ @@ -502,9 +495,7 @@ def get_inverse(self): if (self.theta, self.phi, self.lamda) == (0, 0, 0): return self.__class__(0, 0, 0) else: - return self.__class__(-self.theta + 4 * math.pi, - -self.phi + 4 * math.pi, - -self.lamda + 4 * math.pi) + return self.__class__(-self.theta + 4 * math.pi, -self.phi + 4 * math.pi, -self.lamda + 4 * math.pi) def get_merged(self, other): """ @@ -524,16 +515,13 @@ def get_merged(self, other): New object representing the merged gates. """ if isinstance(other, self.__class__): - return self.__class__(self.theta + other.theta, - self.phi + other.phi, - self.lamda + other.lamda) + return self.__class__(self.theta + other.theta, self.phi + other.phi, self.lamda + other.lamda) raise NotMergeable("Can't merge different types of rotation gates.") def __eq__(self, other): - """ Return True if same class and same rotation angle. """ + """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): - return ((self.theta, self.phi, self.lamda) - == (other.theta, other.phi, other.lamda)) + return (self.theta, self.phi, self.lamda) == (other.theta, other.phi, other.lamda) else: return False @@ -547,9 +535,11 @@ def is_identity(self): """ Return True if the gate is equivalent to an Identity gate """ - return ((self.theta == 0. or self.theta == 4 * math.pi) - and (self.phi == 0. or self.phi == 4 * math.pi) - and (self.lamda == 0. or self.lamda == 4 * math.pi)) + return ( + (self.theta == 0.0 or self.theta == 4 * math.pi) + and (self.phi == 0.0 or self.phi == 4 * math.pi) + and (self.lamda == 0.0 or self.lamda == 4 * math.pi) + ) class BasicPhaseGate(BasicGate): diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 75bda79f..f098e7fd 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -212,7 +212,7 @@ def test_basic_rotation_gate_is_identity(): assert basic_rotation_gate5.is_identity() -def test_u3_gate_comparison_and_hash(): +def test_basic_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) basic_rotation_gate2 = _basics.BasicRotationGate(0.5) basic_rotation_gate3 = _basics.BasicRotationGate(0.5 + 4 * math.pi) @@ -234,9 +234,11 @@ def test_u3_gate_comparison_and_hash(): assert not basic_gate == basic_rotation_gate6 assert basic_rotation_gate2 != _basics.BasicRotationGate(0.5 + 2 * math.pi) -@pytest.mark.parametrize("input_angle, modulo_angle", - [(2.0, 2.0), (17., 4.4336293856408275), - (-0.5 * math.pi, 3.5 * math.pi), (4 * math.pi, 0)]) + +@pytest.mark.parametrize( + "input_angle, modulo_angle", + [(2.0, 2.0), (17.0, 4.4336293856408275), (-0.5 * math.pi, 3.5 * math.pi), (4 * math.pi, 0)], +) def test_u3_gate_init(input_angle, modulo_angle): # Test internal representation gate = _basics.U3Gate(input_angle, input_angle, input_angle) @@ -247,23 +249,19 @@ def test_u3_gate_init(input_angle, modulo_angle): def test_u3_gate_str(): gate = _basics.U3Gate(math.pi, math.pi, math.pi) - assert (str(gate) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)") + assert str(gate) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)" assert gate.to_string(symbols=True) == u"U3Gate(1.0π,1.0π,1.0π)" - assert (gate.to_string(symbols=False) == - "U3Gate(3.14159265359,3.14159265359,3.14159265359)") + assert gate.to_string(symbols=False) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)" def test_u3_tex_str(): gate = _basics.U3Gate(0.5 * math.pi, 0.5 * math.pi, 0.5 * math.pi) assert gate.tex_str() == "U3Gate$(0.5\\pi,0.5\\pi,0.5\\pi)$" - gate = _basics.U3Gate(4 * math.pi - 1e-13, - 4 * math.pi - 1e-13, - 4 * math.pi - 1e-13) + gate = _basics.U3Gate(4 * math.pi - 1e-13, 4 * math.pi - 1e-13, 4 * math.pi - 1e-13) assert gate.tex_str() == "U3Gate$(0.0\\pi,0.0\\pi,0.0\\pi)$" -@pytest.mark.parametrize("input_angle, inverse_angle", - [(2.0, -2.0 + 4 * math.pi), (-0.5, 0.5), (0.0, 0)]) +@pytest.mark.parametrize("input_angle, inverse_angle", [(2.0, -2.0 + 4 * math.pi), (-0.5, 0.5), (0.0, 0)]) def test_u3_gate_get_inverse(input_angle, inverse_angle): u3_gate = _basics.U3Gate(input_angle, input_angle, input_angle) inverse = u3_gate.get_inverse() @@ -285,15 +283,11 @@ def test_u3_gate_get_merged(): def test_u3_gate_is_identity(): - u3_gate1 = _basics.U3Gate(0., 0., 0.) - u3_gate2 = _basics.U3Gate( - 1. * math.pi, 1. * math.pi, 1. * math.pi) - u3_gate3 = _basics.U3Gate( - 2. * math.pi, 2. * math.pi, 2. * math.pi) - u3_gate4 = _basics.U3Gate( - 3. * math.pi, 3. * math.pi, 3. * math.pi) - u3_gate5 = _basics.U3Gate( - 4. * math.pi, 4. * math.pi, 4. * math.pi) + u3_gate1 = _basics.U3Gate(0.0, 0.0, 0.0) + u3_gate2 = _basics.U3Gate(1.0 * math.pi, 1.0 * math.pi, 1.0 * math.pi) + u3_gate3 = _basics.U3Gate(2.0 * math.pi, 2.0 * math.pi, 2.0 * math.pi) + u3_gate4 = _basics.U3Gate(3.0 * math.pi, 3.0 * math.pi, 3.0 * math.pi) + u3_gate5 = _basics.U3Gate(4.0 * math.pi, 4.0 * math.pi, 4.0 * math.pi) assert u3_gate1.is_identity() assert not u3_gate2.is_identity() assert not u3_gate3.is_identity() @@ -304,8 +298,7 @@ def test_u3_gate_is_identity(): def test_u3_gate_comparison_and_hash(): u3gate1 = _basics.U3Gate(0.5, 0.5, 0.5) u3gate2 = _basics.U3Gate(0.5, 0.5, 0.5) - u3gate3 = _basics.U3Gate( - 0.5 + 4 * math.pi, 0.5 + 4 * math.pi, 0.5 + 4 * math.pi) + u3gate3 = _basics.U3Gate(0.5 + 4 * math.pi, 0.5 + 4 * math.pi, 0.5 + 4 * math.pi) assert u3gate1 == u3gate2 assert hash(u3gate1) == hash(u3gate2) assert u3gate1 == u3gate3 @@ -314,19 +307,15 @@ def test_u3_gate_comparison_and_hash(): # Test __ne__: assert u3gate4 != u3gate1 # Test one gate close to 4*pi the other one close to 0 - u3gate5 = _basics.U3Gate(1.e-13, 1.e-13, 1.e-13) - u3gate6 = _basics.U3Gate(4 * math.pi - 1.e-13, - 4 * math.pi - 1.e-13, - 4 * math.pi - 1.e-13) + u3gate5 = _basics.U3Gate(1.0e-13, 1.0e-13, 1.0e-13) + u3gate6 = _basics.U3Gate(4 * math.pi - 1.0e-13, 4 * math.pi - 1.0e-13, 4 * math.pi - 1.0e-13) assert u3gate5 == u3gate6 assert u3gate6 == u3gate5 assert hash(u3gate5) == hash(u3gate6) # Test different types of gates basic_gate = _basics.BasicGate() assert not basic_gate == u3gate6 - assert u3gate2 != _basics.U3Gate(0.5 + 2 * math.pi, - 0.5 + 2 * math.pi, - 0.5 + 2 * math.pi) + assert u3gate2 != _basics.U3Gate(0.5 + 2 * math.pi, 0.5 + 2 * math.pi, 0.5 + 2 * math.pi) @pytest.mark.parametrize( diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index cf58d5f9..9f38c8e5 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -153,10 +153,12 @@ def test_rz(angle): @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_u3(angle): gate = _gates.U3(angle, angle, angle) - expected_matrix = np.matrix([[cmath.exp(-1j * angle) + math.cos(.5 * angle), - -1 + math.sin(.5 * angle)], - [1 + math.sin(.5 * angle), - cmath.exp(1j * angle) + math.cos(.5 * angle)]]) + expected_matrix = np.matrix( + [ + [cmath.exp(-1j * angle) + math.cos(0.5 * angle), -1 + math.sin(0.5 * angle)], + [1 + math.sin(0.5 * angle), cmath.exp(1j * angle) + math.cos(0.5 * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) @@ -164,10 +166,12 @@ def test_u3(angle): @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_u2(angle): gate = _gates.U2(angle, angle) - expected_matrix = np.matrix([[cmath.exp(-1j * angle) + math.cos(.25 * math.pi), - -1 + math.sin(.25 * math.pi)], - [1 + math.sin(.25 * math.pi), - cmath.exp(1j * angle) + math.cos(.25 * math.pi)]]) + expected_matrix = np.matrix( + [ + [cmath.exp(-1j * angle) + math.cos(0.25 * math.pi), -1 + math.sin(0.25 * math.pi)], + [1 + math.sin(0.25 * math.pi), cmath.exp(1j * angle) + math.cos(0.25 * math.pi)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) From ef01aa31ae55710858b32bc8f219b56ab63cac4e Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:21:59 +0200 Subject: [PATCH 14/42] Update GitHub workflows to install new extras --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84a63d78..8e80e515 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,11 +69,11 @@ jobs: - name: Build and install package (Unix) if: runner.os != 'Windows' - run: python -m pip install -ve .[azure-quantum,braket,revkit,test] + run: python -m pip install -ve .[azure-quantum,braket,revkit,test,qiskit,pyparsing] - name: Build and install package (Windows) if: runner.os == 'Windows' - run: python -m pip install -ve .[azure-quantum,braket,test] + run: python -m pip install -ve .[azure-quantum,braket,test,qiskit,pyparsing] - name: Pytest run: | @@ -155,7 +155,7 @@ jobs: run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[azure-quantum,braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test,qiskit,pyparsing] - name: Pytest run: | @@ -206,7 +206,7 @@ jobs: run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[azure-quantum,braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test,qiskit,pyparsing] - name: Pytest run: | @@ -296,7 +296,7 @@ jobs: - name: Install dependencies run: | python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket,qiskit,pyparsing cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary From 0715877431fa920435b66a9c80eeea4f9a67bd60 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:32:49 +0200 Subject: [PATCH 15/42] Avoid test failures if the extras are not installed --- projectq/libs/qasm/__init__.py | 7 ++----- projectq/libs/qasm/_parse_qasm_pyparsing_test.py | 2 +- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index 3e658124..9ad8246a 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -25,13 +25,10 @@ import warnings err = ( - 'Unable to import either qiskit or pyparsing\n' - 'Please install either of them (e.g. using the ' - 'command python -m pip install qiskit' + 'Unable to import either qiskit or pyparsing\nPlease install either of them if you want to use ' + 'projectq.libs.qasm (e.g. using the command python -m pip install projectq[qiskit])' ) - warnings.warn(err + '\n' 'The provided read_qasm_* functions will systematically' 'raise a RuntimeError') - def read_qasm_file(eng, filename): # pylint: disable=unused-argument raise RuntimeError(err) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index f0884401..99c32d0a 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -19,13 +19,13 @@ from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate from projectq.cengines import MainEngine, DummyEngine from projectq.backends import CommandPrinter -from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str, _CUSTOM_GATES # ============================================================================== _has_pyparsing = True try: import pyparsing # noqa: F401 + from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str, _CUSTOM_GATES except ImportError: _has_pyparsing = False diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index b18a0ffd..29e2a23e 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -19,13 +19,13 @@ from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate from projectq.cengines import MainEngine, DummyEngine from projectq.backends import CommandPrinter -from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str # ============================================================================== _has_qiskit = True try: import qiskit # noqa: F401 + from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str except ImportError: _has_qiskit = False From f5cd716406f33663ffd550e01fbafe3c2f56a36c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:33:44 +0200 Subject: [PATCH 16/42] Change setup.cfg to invalidate cache on GitHub CI --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index be43d4bc..b702fce9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,9 +3,9 @@ zip_safe = False packages = find: -pyparsing = pyparsing qiskit = qiskit qasm = qiskit +pyparsing = pyparsing # ============================================================================== From 6745beba95049abe4a50ad415fa26152628e2738 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:45:24 +0200 Subject: [PATCH 17/42] Fix file permissions issues on GitHub CI --- projectq/libs/qasm/_parse_qasm_pyparsing_test.py | 3 ++- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index 99c32d0a..a306c420 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import platform import pytest import tempfile @@ -233,7 +234,7 @@ def test_read_qasm2_str(dummy_eng, iqft_example): def test_read_qasm2_file(dummy_eng, iqft_example): dummy, eng = dummy_eng - with tempfile.NamedTemporaryFile('w') as fd: + with tempfile.NamedTemporaryFile(mode='w', delete=True if platform.system() != 'Windows' else False) as fd: fd.write(iqft_example) fd.flush() qubits_map, bits_map = read_qasm_file(eng, fd.name) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index 29e2a23e..ab77872f 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import platform import pytest import tempfile @@ -207,7 +208,7 @@ def test_read_qasm2_str(dummy_eng, iqft_example): def test_read_qasm2_file(dummy_eng, iqft_example): dummy, eng = dummy_eng - with tempfile.NamedTemporaryFile('w') as fd: + with tempfile.NamedTemporaryFile(mode='w', delete=True if platform.system() != 'Windows' else False) as fd: fd.write(iqft_example) fd.flush() qubits_map, bits_map = read_qasm_file(eng, fd.name) From 4277ac212f46b724074e5f0e45c8b4593dc056bb Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 13:56:04 +0200 Subject: [PATCH 18/42] Add some comments to setup.cfg --- pyproject.toml | 12 ++++++++++++ setup.cfg | 4 ---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8d3b23af..f0ff33b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,18 @@ braket = [ 'boto3' ] +qiskit = [ + 'qiskit' +] + +qasm = [ + 'qiskit' +] + +pyparsing = [ + 'pyparsing' +] + revkit = [ 'revkit == 3.0a2.dev2', 'dormouse' diff --git a/setup.cfg b/setup.cfg index b702fce9..c5248b57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,10 +3,6 @@ zip_safe = False packages = find: -qiskit = qiskit -qasm = qiskit -pyparsing = pyparsing - # ============================================================================== [flake8] From 64dfcdb6d5ea98da953422abb0305bae7633bc03 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 14:07:47 +0200 Subject: [PATCH 19/42] Update Qiskit requirements in setup.cfg --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0ff33b5..e8c19852 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,11 +52,11 @@ braket = [ ] qiskit = [ - 'qiskit' + 'qiskit>=0.14' ] qasm = [ - 'qiskit' + 'qiskit>=0.14' ] pyparsing = [ From 7e0a60cd247f46f2680e178cd7401aa4c8633d46 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 14:08:19 +0200 Subject: [PATCH 20/42] Use more file for hashing on CI when fetching/saving the cache --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e80e515..9dce9741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -283,9 +283,6 @@ jobs: git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Create pip cache dir - run: mkdir -p ~/.cache/pip - - name: Cache wheels uses: actions/cache@v3 with: From 7c27bcea5cf0def5bea5a2a971a4789b994f79c6 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 14:11:16 +0200 Subject: [PATCH 21/42] Remove unneeded symbol --- projectq/libs/qasm/_parse_qasm_pyparsing_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index a306c420..e1c46e9b 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -26,7 +26,7 @@ _has_pyparsing = True try: import pyparsing # noqa: F401 - from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str, _CUSTOM_GATES + from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str except ImportError: _has_pyparsing = False From 2e10752930c4aa75861073575a7dd579f29cbdee Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 14:19:08 +0200 Subject: [PATCH 22/42] Make sure that the Python wheel package is installed on all CI jobs --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dce9741..f8a36c3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -330,6 +330,7 @@ jobs: - name: Install docs & setup requirements run: | + python3 -m pip install wheel python3 -m pip install .[docs] - name: Build docs From 96bd97c25d73a7aa5d8a3d9fa8e0a9919d1b2558 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 14 Jun 2021 14:57:00 +0200 Subject: [PATCH 23/42] Fix CI build failure with Clang --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a36c3d..1956b88a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,6 +144,11 @@ jobs: libomp-dev --no-install-recommends + - name: Fix Python LDSHARED + run: | + ld_args=$(python3 -c "import sysconfig; print(' '.join(sysconfig.get_config_var('LDSHARED').split()[1:]))") + echo "LDSHARED='clang $ld_args'" >> $GITHUB_ENV + - name: Prepare Python env run: | python3 -m pip install -U pip setuptools wheel From cd9810b9dc7af9ceabb8589bcb3fb492ea62e8ed Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 14:50:02 +0200 Subject: [PATCH 24/42] Changes to make running the CI locally possible --- .github/workflows/ci.yml | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1956b88a..dbe3621e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,10 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + if [ $(git rev-parse --is-shallow-repository) == "true" ]; then + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + fi - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -133,8 +135,10 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + if [ $(git rev-parse --is-shallow-repository) == "true" ]; then + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + fi - name: Prepare env run: > @@ -144,10 +148,12 @@ jobs: libomp-dev --no-install-recommends - - name: Fix Python LDSHARED + - name: Fix environment variables for compilation with Clang run: | - ld_args=$(python3 -c "import sysconfig; print(' '.join(sysconfig.get_config_var('LDSHARED').split()[1:]))") - echo "LDSHARED='clang $ld_args'" >> $GITHUB_ENV + ld_flags=$(python3 -c "import sysconfig; print(' '.join(sysconfig.get_config_var('LDSHARED').split()[1:]))") + echo "LDSHARED=clang" >> $GITHUB_ENV + echo "CFLAGS=-fPIC" >> $GITHUB_ENV + echo "LDFLAGS=$ld_flags" >> $GITHUB_ENV - name: Prepare Python env run: | @@ -190,8 +196,10 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + if [ $(git rev-parse --is-shallow-repository) == "true" ]; then + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + fi - name: Prepare env run: > @@ -285,8 +293,10 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + if [ $(git rev-parse --is-shallow-repository) == "true" ]; then + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + fi - name: Cache wheels uses: actions/cache@v3 @@ -321,8 +331,10 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + if [ $(git rev-parse --is-shallow-repository) == "true" ]; then + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + fi - uses: actions/setup-python@v4 with: From 17fa5c4e8754abe64b8ef971631097f7de7f61d3 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 14:50:32 +0200 Subject: [PATCH 25/42] Add missing dependencies for GCC and Clang CI builds --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbe3621e..78664c95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx python3-pytest python3-pytest-cov python3-flaky - libomp-dev + libomp-dev libopenblas-dev liblapack-dev --no-install-recommends - name: Fix environment variables for compilation with Clang @@ -206,6 +206,7 @@ jobs: apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx python3-pytest python3-pytest-cov python3-flaky + libopenblas-dev liblapack-dev --no-install-recommends - name: Prepare Python env From 15efcba8e1e7a59bd59fc68226b0d2d593579753 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 15:58:46 +0200 Subject: [PATCH 26/42] Fix Clang CI errors due to OpenMP support --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78664c95..e19cd3a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,7 +108,8 @@ jobs: fail-fast: false matrix: clang: - - 3.5 # version for full C++14 support (3.4 fails because of -fstack-protector-strong) + - 3.7 # could be 3.5 version for full C++14 support (3.4 fails because of -fstack-protector-strong) + # but for proper OpenMP support we require 3.7 - 5 # earliest version for reasonable C++17 support - 10 # version for full C++17 support (with patches) - latest From 1d272c2ca617a8349a1f64ec6d0c66db7aecfd14 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 16:33:32 +0200 Subject: [PATCH 27/42] Fix CI build issues on Windows and increase minimum to numpy >= 1.17 --- .github/workflows/ci.yml | 12 +++++++++++- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e19cd3a0..5907dde8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,13 +31,23 @@ jobs: - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} + if: ${{ !env.ACT && runner.os != 'Windows' }} run: | if [ $(git rev-parse --is-shallow-repository) == "true" ]; then git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* fi + - name: Get history and tags for SCM versioning to work (Windows) + if: ${{ !env.ACT && runner.os == 'Windows' }} + run: | + $value = git rev-parse --is-shallow-repository + if ( $value -eq "true" ) + { + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + } + - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 with: diff --git a/pyproject.toml b/pyproject.toml index e8c19852..e2b37b28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dynamic = ["version"] dependencies = [ 'matplotlib >= 2.2.3', 'networkx >= 2', - 'numpy', + 'numpy >= 1.17', # Mainly for Qiskit 'requests', 'scipy' ] From cc6f4013e1aff88a0301c718803ae708ec3a469e Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 18:07:26 +0200 Subject: [PATCH 28/42] Directly require qiskit-terra instead of qiskit --- .github/workflows/ci.yml | 4 +--- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5907dde8..ae53ead9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,14 +156,13 @@ jobs: apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx python3-pytest python3-pytest-cov python3-flaky - libomp-dev libopenblas-dev liblapack-dev + libomp-dev --no-install-recommends - name: Fix environment variables for compilation with Clang run: | ld_flags=$(python3 -c "import sysconfig; print(' '.join(sysconfig.get_config_var('LDSHARED').split()[1:]))") echo "LDSHARED=clang" >> $GITHUB_ENV - echo "CFLAGS=-fPIC" >> $GITHUB_ENV echo "LDFLAGS=$ld_flags" >> $GITHUB_ENV - name: Prepare Python env @@ -217,7 +216,6 @@ jobs: apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx python3-pytest python3-pytest-cov python3-flaky - libopenblas-dev liblapack-dev --no-install-recommends - name: Prepare Python env diff --git a/pyproject.toml b/pyproject.toml index e2b37b28..6d1a46bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,11 +52,11 @@ braket = [ ] qiskit = [ - 'qiskit>=0.14' + 'qiskit-terra>=0.11' ] qasm = [ - 'qiskit>=0.14' + 'qiskit-terra>=0.11' ] pyparsing = [ From c56ddbb81da3ebe5792a3e358a5f120f30cd71fc Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 15 Jun 2021 18:12:35 +0200 Subject: [PATCH 29/42] Remove unrelated CI configuration changes --- .github/workflows/ci.yml | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae53ead9..11f1d21c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,22 +31,10 @@ jobs: - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT && runner.os != 'Windows' }} - run: | - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - fi - - - name: Get history and tags for SCM versioning to work (Windows) - if: ${{ !env.ACT && runner.os == 'Windows' }} + if: ${{ !env.ACT }} run: | - $value = git rev-parse --is-shallow-repository - if ( $value -eq "true" ) - { - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - } + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v4 @@ -118,8 +106,7 @@ jobs: fail-fast: false matrix: clang: - - 3.7 # could be 3.5 version for full C++14 support (3.4 fails because of -fstack-protector-strong) - # but for proper OpenMP support we require 3.7 + - 3.5 # version for full C++14 support (3.4 fails because of -fstack-protector-strong) - 5 # earliest version for reasonable C++17 support - 10 # version for full C++17 support (with patches) - latest @@ -146,10 +133,8 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - fi + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Prepare env run: > @@ -206,10 +191,8 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - fi + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Prepare env run: > @@ -341,10 +324,8 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - fi + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* - uses: actions/setup-python@v4 with: From 86724a53b05c4e45cced3d774a767901db98960d Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 16 Jun 2021 11:20:55 +0200 Subject: [PATCH 30/42] Fix error linked to Qiskit minimum version required --- projectq/libs/qasm/_parse_qasm_qiskit.py | 4 ++-- pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index e89d781c..5227fa64 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -55,9 +55,9 @@ def apply_op(eng, gate, qubits, bits, bits_map): # TODO: This will silently discard opaque gates... return - gate_args = {gate._definition.qregs[0].name: qubits} + gate_args = {gate.definition.qregs[0].name: qubits} - for gate_sub, quregs_sub, bits_sub in gate._definition.data: + for gate_sub, quregs_sub, bits_sub in gate.definition.data: # OpenQASM 2.0 limitation... assert gate.name != 'measure' and not bits_sub apply_op( diff --git a/pyproject.toml b/pyproject.toml index 6d1a46bc..f3f3e0ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,11 +52,11 @@ braket = [ ] qiskit = [ - 'qiskit-terra>=0.11' + 'qiskit-terra>=0.15' ] qasm = [ - 'qiskit-terra>=0.11' + 'qiskit-terra>=0.15' ] pyparsing = [ From 14c554edcc1ee11daa9fb454511e8abfd7cd814b Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 16 Jun 2021 14:46:28 +0200 Subject: [PATCH 31/42] Add support for negative control for OpenQASMBackend --- projectq/backends/_qasm.py | 9 ++++++--- projectq/backends/_qasm_test.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py index fa99a4f3..948ca72e 100644 --- a/projectq/backends/_qasm.py +++ b/projectq/backends/_qasm.py @@ -17,7 +17,7 @@ from copy import deepcopy from projectq.cengines import BasicEngine -from projectq.meta import get_control_count +from projectq.meta import get_control_count, has_negative_control from projectq.ops import ( X, NOT, @@ -41,6 +41,7 @@ FlushGate, ) + # ============================================================================== @@ -115,9 +116,11 @@ def is_available(self, cmd): Args: cmd (Command): Command for which to check availability """ - gate = cmd.gate - n_controls = get_control_count(cmd) + if has_negative_control(cmd): + return False + n_controls = get_control_count(cmd) + gate = cmd.gate is_available = False if gate in (Measure, Allocate, Deallocate, Barrier): diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index 251ba27c..cfad9cb5 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -42,6 +42,7 @@ Command, All, ) +from projectq.types import WeakQubitRef from ._qasm import OpenQASMBackend # ============================================================================== @@ -206,6 +207,22 @@ def test_qasm_is_available_2control(gate, is_available): assert eng.is_available(cmd) == is_available +def test_ibm_backend_is_available_negative_control(): + backend = OpenQASMBackend() + backend.is_last_engine = True + + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + def test_qasm_test_qasm_single_qubit_gates(): eng = MainEngine(backend=OpenQASMBackend(), engine_list=[]) qubit = eng.allocate_qubit() From 0263f3888a4ad4bd5a5730e7d063ef3155ab6f78 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 16 Jun 2021 15:05:21 +0200 Subject: [PATCH 32/42] Fixing a few minor issues --- projectq/libs/qasm/__init__.py | 2 -- projectq/libs/qasm/_utils.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index 9ad8246a..6339378b 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -22,8 +22,6 @@ try: from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str except ImportError as e: - import warnings - err = ( 'Unable to import either qiskit or pyparsing\nPlease install either of them if you want to use ' 'projectq.libs.qasm (e.g. using the command python -m pip install projectq[qiskit])' diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py index 566e5656..65e4f57b 100644 --- a/projectq/libs/qasm/_utils.py +++ b/projectq/libs/qasm/_utils.py @@ -25,7 +25,7 @@ def __init__(self, name, params): Constructor Args: - name (str): Name/type of gat + name (str): Name/type of the quantum gate params (list,tuple): Parameter for the gate (may be empty) """ @@ -50,7 +50,6 @@ def apply_gate(gate, qubits): """ Apply a gate to some qubits while separating control and target qubits. - Args: gate (BasicGate): Instance of a ProjectQ gate qubits (list): List of ProjectQ qubits the gate applies to. From 5f450bf4158d2dc4e8688ae77a0d816e234d4ea0 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 16 Jun 2021 15:15:21 +0200 Subject: [PATCH 33/42] Add missing license header --- projectq/libs/qasm/_qiskit_conv.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index a05755ac..72143b6b 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -1,4 +1,17 @@ # -*- coding: utf-8 -*- +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# 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 +# +# http://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 projectq.ops import Barrier, H, S, Sdagger, T, Tdagger, X, Y, Z, Rx, Ry, Rz, Swap, Toffoli, C, CNOT, U2, U3 # ============================================================================== From 952cdce6992d98207a70cf0101cbc0741c28189c Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 23 Jun 2021 18:19:16 +0200 Subject: [PATCH 34/42] Update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11f1d21c..c56b033d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,12 +49,12 @@ jobs: - name: Generate requirement file (Unix) if: runner.os != 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit,qiskit,pyparsing - name: Generate requirement file (Windows) if: runner.os == 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,qiskit,pyparsing - name: Prepare env run: | From a2c9a84183feb03d3bba871f67bc86b3ed6376c9 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 23 Jun 2021 21:33:29 +0200 Subject: [PATCH 35/42] Fix issues flagged by linters/formatters --- .pre-commit-config.yaml | 2 +- projectq/backends/_qasm.py | 71 +++++++++------------ projectq/backends/_qasm_test.py | 4 +- projectq/libs/qasm/__init__.py | 14 ++-- projectq/libs/qasm/_parse_qasm_pyparsing.py | 7 +- projectq/libs/qasm/_parse_qasm_qiskit.py | 35 ++++------ projectq/libs/qasm/_qiskit_conv.py | 70 +++++++++++++------- projectq/libs/qasm/_utils.py | 6 +- projectq/ops/_basics.py | 6 +- projectq/ops/_gates.py | 9 +++ 10 files changed, 118 insertions(+), 106 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bbc0be3..e75878e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,7 +117,7 @@ repos: hooks: - id: pylint args: ['--score=n', '--disable=no-member'] - additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx] + additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx, qiskit-terra, pyparsing] - repo: https://github.com/mgedmin/check-manifest rev: '0.49' diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py index 948ca72e..8d5eb72f 100644 --- a/projectq/backends/_qasm.py +++ b/projectq/backends/_qasm.py @@ -45,54 +45,43 @@ # ============================================================================== -class OpenQASMBackend(BasicEngine): +class OpenQASMBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ - Engine to convert ProjectQ commands to OpenQASM format (either string or - file) + Engine to convert ProjectQ commands to OpenQASM format (either string or file) """ def __init__( self, - collate=True, collate_callback=None, - qubit_callback=lambda qubit_id: 'q{}'.format(qubit_id), - bit_callback=lambda qubit_id: 'c{}'.format(qubit_id), + qubit_callback='q{}'.format, + bit_callback='c{}'.format, qubit_id_mapping_redux=True, ): """ Initialize an OpenQASMBackend object. - Contrary to OpenQASM, ProjectQ does not impose the restriction that a - programm must start with qubit/bit allocations and end with some - measurements. + Contrary to OpenQASM, ProjectQ does not impose the restriction that a programm must start with qubit/bit + allocations and end with some measurements. - The user can configure what happens each time a FlushGate() is - encountered by setting the `collate` and `collate_func` arguments to - an OpenQASMBackend constructor, + The user can configure what happens each time a FlushGate() is encountered by setting the `collate` and + `collate_func` arguments to an OpenQASMBackend constructor, Args: output (list,file): - collate (bool): If True, simply append commands to the exisiting - file/string list when a FlushGate is received. If False, you - need to specify `collate_callback` arguments as well. - collate (function): Only has an effect if `collate` is False. Each - time a FlushGate is received, this callback function will be - called. + collate (function): Each time a FlushGate is received, this callback function will be called. If let + unspecified, the commands are appended to the existing file/string. Function signature: Callable[[Sequence[str]], None] - qubit_callback (function): Callback function called upon create of - each qubit to generate a name for the qubit. + qubit_callback (function): Callback function called upon create of each qubit to generate a name for the + qubit. Function signature: Callable[[int], str] - bit_callback (function): Callback function called upon create of - each qubit to generate a name for the qubit. + bit_callback (function): Callback function called upon create of each qubit to generate a name for the + qubit. Function signature: Callable[[int], str] - qubit_id_mapping_redux (bool): If True, try to allocate new Qubit - IDs to the next available qreg/creg (if any), otherwise create - a new qreg/creg. If False, simply create a new qreg/creg for - each new Qubit ID + qubit_id_mapping_redux (bool): If True, try to allocate new Qubit IDs to the next available qreg/creg (if + any), otherwise create a new qreg/creg. If False, simply create a new qreg/creg for each new Qubit ID """ super().__init__() - self._collate = collate - self._collate_callback = None if collate else collate_callback + self._collate_callback = collate_callback self._gen_qubit_name = qubit_callback self._gen_bit_name = bit_callback self._qubit_id_mapping_redux = qubit_id_mapping_redux @@ -107,6 +96,9 @@ def __init__( @property def qasm(self): + """ + Access to the QASM representation of the circuit. + """ return self._output def is_available(self, cmd): @@ -150,8 +142,7 @@ def is_available(self, cmd): return False if not self.is_last_engine: return self.next_engine.is_available(cmd) - else: - return True + return True def receive(self, command_list): """ @@ -170,12 +161,11 @@ def receive(self, command_list): if not self.is_last_engine: self.send(command_list) - def _store(self, cmd): + def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements """ Temporarily store the command cmd. - Translates the command and stores it the _openqasm_circuit attribute - (self._openqasm_circuit) + Translates the command and stores it the _openqasm_circuit attribute (self._openqasm_circuit) Args: cmd: Command to store @@ -223,9 +213,8 @@ def _format_angle(angle): if gate == Allocate: add = True - # Perform qubit index reduction if possible. This typically means - # that existing qubit keep their indices between FlushGates but - # that qubit indices of deallocated qubit may be reused. + # Perform qubit index reduction if possible. This typically means that existing qubit keep their indices + # between FlushGates but that qubit indices of deallocated qubit may be reused. if self._qubit_id_mapping_redux and self._available_indices: add = False index = self._available_indices.pop() @@ -263,8 +252,8 @@ def _format_angle(angle): try: self._output.append('{} {};'.format(_ccontrolled_gates_func[gate], ','.join(controls + targets))) - except KeyError: - raise RuntimeError('Unable to perform {} gate with n=2 control qubits'.format(gate)) + except KeyError as err: + raise RuntimeError('Unable to perform {} gate with n=2 control qubits'.format(gate)) from err elif n_controls == 1: target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] @@ -300,8 +289,8 @@ def _format_angle(angle): _controlled_gates_func[gate], self._qreg_dict[cmd.control_qubits[0].id], *target_qureg ) ) - except KeyError: - raise RuntimeError('Unable to perform {} gate with n=1 control qubits'.format(gate)) + except KeyError as err: + raise RuntimeError('Unable to perform {} gate with n=1 control qubits'.format(gate)) from err else: target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] if isinstance(gate, Ph): @@ -323,7 +312,7 @@ def _reset_after_flush(self): """ Reset the internal quantum circuit after a FlushGate """ - if self._collate: + if not self._collate_callback: self._output.append('# ' + '=' * 80) else: self._collate_callback(deepcopy(self._output)) diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index cfad9cb5..4c6d1fcc 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -346,9 +346,7 @@ def test_qasm_no_collate(): def _process(output): qasm_list.append(output) - eng = MainEngine( - backend=OpenQASMBackend(collate=False, collate_callback=_process, qubit_id_mapping_redux=False), engine_list=[] - ) + eng = MainEngine(backend=OpenQASMBackend(collate_callback=_process, qubit_id_mapping_redux=False), engine_list=[]) qubit = eng.allocate_qubit() ctrls = eng.allocate_qureg(2) diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index 6339378b..0db8b575 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -21,16 +21,22 @@ except ImportError: # pragma: no cover try: from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str - except ImportError as e: - err = ( + except ImportError: + ERROR_MSG = ( 'Unable to import either qiskit or pyparsing\nPlease install either of them if you want to use ' 'projectq.libs.qasm (e.g. using the command python -m pip install projectq[qiskit])' ) def read_qasm_file(eng, filename): + """ + Dummy implementation + """ # pylint: disable=unused-argument - raise RuntimeError(err) + raise RuntimeError(ERROR_MSG) def read_qasm_str(eng, qasm_str): + """ + Dummy implementation + """ # pylint: disable=unused-argument - raise RuntimeError(err) + raise RuntimeError(ERROR_MSG) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index 459b8e40..0f6d7481 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -434,7 +434,7 @@ def __init__(self, s, loc, toks): self.params = param_str[param_str.find('(') + 1 : param_str.rfind(')')].split(',') # noqa: E203 self.qubits = [QubitProxy(qubit) for qubit in toks[2]] - def eval(self, eng): + def eval(self, eng): # pylint: disable=too-many-branches """ Evaluate a GateOp @@ -442,10 +442,7 @@ def eval(self, eng): eng (projectq.BasicEngine): ProjectQ MainEngine to use """ if self.name in gates_conv_table: - if self.params: - gate = gates_conv_table[self.name](*[parse_expr(p) for p in self.params]) - else: - gate = gates_conv_table[self.name] + gate = gates_conv_table[self.name](*[parse_expr(p) for p in self.params]) qubits = [] for qureg in [qubit.eval(eng) for qubit in self.qubits]: diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 5227fa64..685192e2 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -14,10 +14,10 @@ # limitations under the License. """ Define function to read OpenQASM file format (using Qiskit). """ -from projectq.ops import All, Measure - from qiskit.circuit import QuantumCircuit, Clbit +from projectq.ops import All, Measure + from ._qiskit_conv import gates_conv_table from ._utils import apply_gate @@ -28,9 +28,8 @@ def apply_op(eng, gate, qubits, bits, bits_map): """ Apply a qiskit operation. - This function takes care of converting between qiskit gates and ProjectQ - gates, as well as handling the translation between qiskit's and ProjectQ's - qubit and bits. + This function takes care of converting between qiskit gates and ProjectQ gates, as well as handling the + translation between qiskit's and ProjectQ's qubit and bits. Args: eng (MainEngine): MainEngine to use to the operation(s) @@ -64,10 +63,7 @@ def apply_op(eng, gate, qubits, bits, bits_map): eng, gate_sub, [gate_args[qubit.register.name][qubit.index] for qubit in quregs_sub], [], bits_map ) else: - if gate.params: - gate_projectq = gates_conv_table[gate.name](*gate.params) - else: - gate_projectq = gates_conv_table[gate.name] + gate_projectq = gates_conv_table[gate.name](*gate.params) if gate.condition: # OpenQASM 2.0 @@ -100,8 +96,7 @@ def _convert_qiskit_circuit(eng, circuit): circuit (qiskit.QuantumCircuit): Quantum circuit to process Note: - At this time, we support most of OpenQASM 2.0 and some of 3.0, - although the latter is still experimental. + At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ # Create maps between qiskit and ProjectQ for qubits and bits qubits_map = {qureg.name: eng.allocate_qureg(qureg.size) for qureg in circuit.qregs} @@ -119,19 +114,16 @@ def _convert_qiskit_circuit(eng, circuit): def read_qasm_str(eng, qasm_str): """ - Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to - ProjectQ commands. + Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to ProjectQ commands. - This version of the function uses Qiskit in order to parse the *.qasm - file. + This version of the function uses Qiskit in order to parse the *.qasm file. Args: eng (MainEngine): MainEngine to use for creating qubits and commands. filename (string): Path to *.qasm file Note: - At this time, we support most of OpenQASM 2.0 and some of 3.0, - although the latter is still experimental. + At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ circuit = QuantumCircuit.from_qasm_str(qasm_str) return _convert_qiskit_circuit(eng, circuit) @@ -142,19 +134,16 @@ def read_qasm_str(eng, qasm_str): def read_qasm_file(eng, filename): """ - Read an OpenQASM (2.0, 3.0 is experimental) file and convert it to - ProjectQ commands. + Read an OpenQASM (2.0, 3.0 is experimental) file and convert it to ProjectQ commands. - This version of the function uses Qiskit in order to parse the *.qasm - file. + This version of the function uses Qiskit in order to parse the *.qasm file. Args: eng (MainEngine): MainEngine to use for creating qubits and commands. filename (string): Path to *.qasm file Note: - At this time, we support most of OpenQASM 2.0 and some of 3.0, - although the latter is still experimental. + At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ circuit = QuantumCircuit.from_qasm_file(filename) diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index 72143b6b..ec0dcd5b 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -12,39 +12,61 @@ # 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 projectq.ops import Barrier, H, S, Sdagger, T, Tdagger, X, Y, Z, Rx, Ry, Rz, Swap, Toffoli, C, CNOT, U2, U3 + +"""Contains some helper variables for the Qiskit conversion functions""" + +from projectq.ops import ( + Barrier, + HGate, + SGate, + Sdagger, + TGate, + Tdagger, + XGate, + YGate, + ZGate, + Rx, + Ry, + Rz, + SwapGate, + Toffoli, + C, + CNOT, + U2, + U3, +) # ============================================================================== # Conversion map between Qiskit gate names and ProjectQ gates gates_conv_table = { - 'barrier': Barrier, - 'h': H, - 's': S, - 'sdg': Sdagger, - 't': T, - 'tdg': Tdagger, - 'x': X, - 'y': Y, - 'z': Z, - 'swap': Swap, - 'rx': lambda a: Rx(a), - 'ry': lambda a: Ry(a), - 'rz': lambda a: Rz(a), - 'u1': lambda a: Rz(a), - 'u2': lambda p, l: U2(p, l), - 'u3': lambda t, p, l: U3(t, p, l), - 'phase': lambda a: Rz(a), + 'barrier': lambda: Barrier, + 'h': HGate, + 's': SGate, + 'sdg': lambda: Sdagger, + 't': TGate, + 'tdg': lambda: Tdagger, + 'x': XGate, + 'y': YGate, + 'z': ZGate, + 'swap': SwapGate, + 'rx': Rx, + 'ry': Ry, + 'rz': Rz, + 'u1': Rz, + 'u2': U2, + 'u3': U3, + 'phase': Rz, # Controlled gates - 'ch': C(H), - 'cx': CNOT, - 'cy': C(Y), - 'cz': C(Z), - 'cswap': C(Swap), + 'ch': lambda: C(HGate()), + 'cx': lambda: CNOT, + 'cy': lambda: C(YGate()), + 'cz': lambda: C(ZGate()), + 'cswap': lambda: C(SwapGate()), 'crz': lambda a: C(Rz(a)), 'cu1': lambda a: C(Rz(a)), 'cu2': lambda p, l: C(U2(p, l)), 'cu3': lambda t, p, l: C(U3(t, p, l)), # Doubly-controlled gates - "ccx": Toffoli, + "ccx": lambda: Toffoli, } diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py index 65e4f57b..8ffef3f6 100644 --- a/projectq/libs/qasm/_utils.py +++ b/projectq/libs/qasm/_utils.py @@ -20,6 +20,10 @@ class OpaqueGate(BasicGate): + """ + Gate representing an opaque gate type. + """ + def __init__(self, name, params): """ Constructor @@ -54,7 +58,7 @@ def apply_gate(gate, qubits): gate (BasicGate): Instance of a ProjectQ gate qubits (list): List of ProjectQ qubits the gate applies to. """ - # pylint: disable = protected-access,pointless-statement + # pylint: disable = protected-access if isinstance(gate, ControlledGate): ctrls = qubits[: gate._n] diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index df6e6bfc..2f644636 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -494,8 +494,7 @@ def get_inverse(self): """ if (self.theta, self.phi, self.lamda) == (0, 0, 0): return self.__class__(0, 0, 0) - else: - return self.__class__(-self.theta + 4 * math.pi, -self.phi + 4 * math.pi, -self.lamda + 4 * math.pi) + return self.__class__(-self.theta + 4 * math.pi, -self.phi + 4 * math.pi, -self.lamda + 4 * math.pi) def get_merged(self, other): """ @@ -522,8 +521,7 @@ def __eq__(self, other): """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): return (self.theta, self.phi, self.lamda) == (other.theta, other.phi, other.lamda) - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 876ee7b5..a68d7996 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -311,8 +311,13 @@ def matrix(self): class U3(U3Gate): + """ + U3 rotation gate class + """ + @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [ @@ -328,6 +333,10 @@ def matrix(self): class U2(U3): + """ + U2 rotation gate class + """ + def __init__(self, phi, lamda): super().__init__(math.pi / 2, phi, lamda) From 559c592dc4873d1935eea991808665e8a5e3b5d9 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Tue, 4 Jan 2022 15:51:45 +0100 Subject: [PATCH 36/42] Fix warnings from linters/formatters --- projectq/backends/_qasm.py | 46 ++-- projectq/backends/_qasm_test.py | 34 +-- projectq/libs/qasm/_parse_qasm_pyparsing.py | 226 +++++++----------- .../libs/qasm/_parse_qasm_pyparsing_test.py | 8 +- projectq/libs/qasm/_parse_qasm_qiskit.py | 6 +- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 8 +- projectq/libs/qasm/_pyparsing_expr.py | 32 ++- projectq/libs/qasm/_pyparsing_expr_test.py | 3 +- projectq/libs/qasm/_qiskit_conv.py | 24 +- projectq/libs/qasm/_utils.py | 14 +- projectq/libs/qasm/_utils_test.py | 27 ++- projectq/ops/_basics.py | 14 +- projectq/ops/_gates.py | 17 +- 13 files changed, 210 insertions(+), 249 deletions(-) diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py index 8d5eb72f..33811697 100644 --- a/projectq/backends/_qasm.py +++ b/projectq/backends/_qasm.py @@ -12,43 +12,40 @@ # 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. -""" Backend to convert ProjectQ commands to OpenQASM. """ +"""Backend to convert ProjectQ commands to OpenQASM.""" from copy import deepcopy from projectq.cengines import BasicEngine from projectq.meta import get_control_count, has_negative_control from projectq.ops import ( - X, NOT, - Y, - Z, - T, - Tdag, - S, - Sdag, + Allocate, + Barrier, + Deallocate, + FlushGate, H, + Measure, Ph, R, Rx, Ry, Rz, + S, + Sdag, Swap, - Measure, - Allocate, - Deallocate, - Barrier, - FlushGate, + T, + Tdag, + X, + Y, + Z, ) - # ============================================================================== class OpenQASMBackend(BasicEngine): # pylint: disable=too-many-instance-attributes - """ - Engine to convert ProjectQ commands to OpenQASM format (either string or file) - """ + """Engine to convert ProjectQ commands to OpenQASM format (either string or file).""" def __init__( self, @@ -87,8 +84,8 @@ def __init__( self._qubit_id_mapping_redux = qubit_id_mapping_redux self._output = [] - self._qreg_dict = dict() - self._creg_dict = dict() + self._qreg_dict = {} + self._creg_dict = {} self._reg_index = 0 self._available_indices = [] @@ -96,9 +93,7 @@ def __init__( @property def qasm(self): - """ - Access to the QASM representation of the circuit. - """ + """Access to the QASM representation of the circuit.""" return self._output def is_available(self, cmd): @@ -146,8 +141,7 @@ def is_available(self, cmd): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receives a command list and, for each command, stores it until completion. Args: command_list: List of commands to execute @@ -309,9 +303,7 @@ def _insert_openqasm_header(self): self._output.append('include "stdgates.inc";') def _reset_after_flush(self): - """ - Reset the internal quantum circuit after a FlushGate - """ + """Reset the internal quantum circuit after a FlushGate.""" if not self._collate_callback: self._output.append('# ' + '=' * 80) else: diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index 4c6d1fcc..65ccd385 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -14,35 +14,37 @@ # limitations under the License. """Tests for projectq.cengines._openqasm.py.""" +import re + import pytest -import re -from projectq.cengines import MainEngine, DummyEngine +from projectq.cengines import DummyEngine, MainEngine from projectq.meta import Control from projectq.ops import ( - X, NOT, - Y, - Z, - T, - Tdagger, - S, - Sdagger, + All, + Allocate, + Barrier, + Command, + Deallocate, + Entangle, H, + Measure, Ph, R, Rx, Ry, Rz, - Allocate, - Deallocate, - Measure, - Barrier, - Entangle, - Command, - All, + S, + Sdagger, + T, + Tdagger, + X, + Y, + Z, ) from projectq.types import WeakQubitRef + from ._qasm import OpenQASMBackend # ============================================================================== diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index 0f6d7481..c94ba19a 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -12,51 +12,48 @@ # 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. -""" -Contains the main engine of every compiler engine pipeline, called MainEngine. -""" +"""Contains the main engine of every compiler engine pipeline, called MainEngine.""" -from copy import deepcopy import operator as op +from copy import deepcopy + from pyparsing import ( - Literal, - Word, - Group, - Or, CharsNotIn, - Optional, + Empty, + Group, + Literal, OneOrMore, - ZeroOrMore, - nestedExpr, + Optional, + Or, Suppress, - removeQuotes, - Empty, + Word, + ZeroOrMore, + alphanums, + alphas, cppStyleComment, cStyleComment, dblQuotedString, - alphas, - alphanums, + nestedExpr, pyparsing_common, + removeQuotes, ) from projectq.ops import All, Measure from ._pyparsing_expr import eval_expr from ._qiskit_conv import gates_conv_table -from ._utils import apply_gate, OpaqueGate +from ._utils import OpaqueGate, apply_gate # ============================================================================== -_QISKIT_VARS = dict() -_BITS_VARS = dict() -_CUSTOM_GATES = dict() -_OPAQUE_GATES = dict() +_QISKIT_VARS = {} +_BITS_VARS = {} +_CUSTOM_GATES = {} +_OPAQUE_GATES = {} class CommonTokens: - """ - Some general tokens - """ + """Some general tokens.""" # pylint: disable = too-few-public-methods @@ -77,16 +74,20 @@ class CommonTokens: class QASMVersionOp: - """ - OpenQASM version. - """ + """OpenQASM version.""" def __init__(self, toks): + """ + Initialize a QASMVersionOp object. + + Args: + toks (pyparsing.Tokens): Pyparsing tokens + """ self.version = toks[0] def eval(self, _): """ - Evaluate a QASMVersionOp + Evaluate a QASMVersionOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -94,20 +95,16 @@ def eval(self, _): # pylint: disable=unused-argument def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" return 'QASMVersionOp({})'.format(self.version) class IncludeOp: - """ - Include file operation. - """ + """Include file operation.""" def __init__(self, toks): """ - Constructor + Initialize an IncludeOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -116,7 +113,7 @@ def __init__(self, toks): def eval(self, _): """ - Evaluate a IncludeOp + Evaluate a IncludeOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -127,9 +124,7 @@ def eval(self, _): raise RuntimeError('Invalid cannot read: {}! (unsupported)'.format(self.fname)) def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" return 'IncludeOp({})'.format(self.fname) @@ -137,13 +132,11 @@ def __repr__(self): # pragma: nocover class QubitProxy: - """ - Qubit access proxy class - """ + """Qubit access proxy class.""" def __init__(self, toks): """ - Constructor + Initialize a QubitProxy object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -157,7 +150,7 @@ def __init__(self, toks): def eval(self, _): """ - Evaluate a QubitProxy + Evaluate a QubitProxy. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -167,9 +160,7 @@ def eval(self, _): return _QISKIT_VARS[self.name] def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" if self.index is not None: return 'Qubit({}[{}])'.format(self.name, self.index) return 'Qubit({})'.format(self.name) @@ -179,15 +170,13 @@ def __repr__(self): # pragma: nocover class VarDeclOp: - """ - Variable declaration operation. - """ + """Variable declaration operation.""" # pylint: disable = too-few-public-methods def __init__(self, type_t, nbits, name, init): """ - Constructor + Initialize a VarDeclOp object. Args: type_t (str): Type of variable @@ -201,9 +190,7 @@ def __init__(self, type_t, nbits, name, init): self.init = init def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" if self.init: return "{}({}, {}, {}) = {}".format(self.__class__.__name__, self.type_t, self.nbits, self.name, self.init) @@ -214,15 +201,13 @@ def __repr__(self): # pragma: nocover class QVarOp(VarDeclOp): - """ - Quantum variable declaration operation. - """ + """Quantum variable declaration operation.""" # pylint: disable = too-few-public-methods def eval(self, eng): """ - Evaluate a QVarOp + Evaluate a QVarOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -237,15 +222,13 @@ def eval(self, eng): class CVarOp(VarDeclOp): - """ - Classical variable declaration operation. - """ + """Classical variable declaration operation.""" # pylint: disable = too-few-public-methods def eval(self, _): """ - Evaluate a CVarOp + Evaluate a CVarOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -276,13 +259,11 @@ def eval(self, _): class GateDefOp: - """ - Operation representing a gate definition. - """ + """Operation representing a gate definition.""" def __init__(self, toks): """ - Constructor + Initialize a GateDefOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -296,7 +277,7 @@ def __init__(self, toks): def eval(self, _): """ - Evaluate a GateDefOp + Evaluate a GateDefOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -304,9 +285,7 @@ def eval(self, _): _CUSTOM_GATES[self.name] = (self.params, self.qparams, self.body) def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" return "GateDefOp({}, {}, {})\n\t{}".format(self.name, self.params, self.qparams, self.body) @@ -314,11 +293,11 @@ def __repr__(self): # pragma: nocover class MeasureOp: - """Measurement operations (OpenQASM 2.0 & 3.0)""" + """Measurement operations (OpenQASM 2.0 & 3.0).""" def __init__(self, toks): """ - Constructor + Initialize a MeasureOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -336,7 +315,7 @@ def __init__(self, toks): def eval(self, eng): """ - Evaluate a MeasureOp + Evaluate a MeasureOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -365,9 +344,7 @@ def eval(self, eng): bits[idx] = bool(qubit) def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" return 'MeasureOp({}, {})'.format(self.qubits, self.bits) @@ -375,13 +352,11 @@ def __repr__(self): # pragma: nocover class OpaqueDefOp: - """ - Opaque gate definition operation. - """ + """Opaque gate definition operation.""" def __init__(self, toks): """ - Constructor + Initialize an OpaqueDefOp object. Args: name (str): Name/type of gat @@ -393,7 +368,7 @@ def __init__(self, toks): def eval(self, _): """ - Evaluate a OpaqueDefOp + Evaluate a OpaqueDefOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -401,9 +376,7 @@ def eval(self, _): _OPAQUE_GATES[self.name] = OpaqueGate(self.name, self.params) def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" if self.params: return 'OpaqueOp({}, {})'.format(self.name, self.params) return 'OpaqueOp({})'.format(self.name) @@ -413,13 +386,11 @@ def __repr__(self): # pragma: nocover class GateOp: - """ - Gate applied to qubits operation. - """ + """Gate applied to qubits operation.""" def __init__(self, s, loc, toks): """ - Constructor + Initialize a GateOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -436,7 +407,7 @@ def __init__(self, s, loc, toks): def eval(self, eng): # pylint: disable=too-many-branches """ - Evaluate a GateOp + Evaluate a GateOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -492,9 +463,7 @@ def eval(self, eng): # pylint: disable=too-many-branches raise RuntimeError('Unknown gate: {}'.format(gate_str)) def __repr__(self): # pragma: nocover - """ - Mainly for debugging - """ + """Mainly for debugging.""" return 'GateOp({}, {}, {})'.format(self.name, self.params, self.qubits) @@ -502,13 +471,11 @@ def __repr__(self): # pragma: nocover class AssignOp: # pragma: nocover - """ - Variable assignment operation (OpenQASM 3.0 only) - """ + """Variable assignment operation (OpenQASM 3.0 only).""" def __init__(self, toks): """ - Constructor + Initialize an AssignOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -518,7 +485,7 @@ def __init__(self, toks): def eval(self, _): """ - Evaluate a AssignOp + Evaluate a AssignOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -533,9 +500,7 @@ def eval(self, _): return 0 def __repr__(self): - """ - Mainly for debugging - """ + """Mainly for debugging.""" return 'AssignOp({},{})'.format(self.var, self.value) @@ -559,9 +524,7 @@ def _parse_if_conditional(if_str): class IfOp: - """ - Operation representing a conditional expression (if-expr). - """ + """Operation representing a conditional expression (if-expr).""" greater = Literal('>').addParseAction(lambda: op.gt) greater_equal = Literal('>=').addParseAction(lambda: op.ge) @@ -576,7 +539,7 @@ class IfOp: def __init__(self, if_str, loc, toks): """ - Constructor + Initialize an IfOp object. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -598,7 +561,7 @@ def __init__(self, if_str, loc, toks): def eval(self, eng): """ - Evaluate a IfOp + Evaluate a IfOp. Args: eng (projectq.BasicEngine): ProjectQ MainEngine to use @@ -620,6 +583,7 @@ def eval(self, eng): gate.eval(eng) def __repr__(self): # pragma: nocover + """Mainly for debugging.""" return "IfExpr({} {} {}) {{ {} }}".format(self.bit, self.binary_op, self.comp_expr, self.body) @@ -628,13 +592,11 @@ def __repr__(self): # pragma: nocover def create_var_decl(toks): """ - Callback function to create either a classical or quantum variable - operation. + Create either a classical or a quantum variable operation. - Args: - toks (pyparsing.Tokens): Pyparsing tokens + Args: + toks (pyparsing.Tokens): Pyparsing tokens """ - type_t = toks[0] names = [] @@ -697,7 +659,7 @@ def _get_nbits(_): def parse_expr(expr_str): """ - Callback function to parse an (mathematical) expression. + Parse a mathematical expression. Args: expr_str (str): Expression to evaluate @@ -709,11 +671,10 @@ def parse_expr(expr_str): class QiskitParser: - """ - Qiskit parser class - """ + """Qiskit parser class.""" def __init__(self): + """Initialize a QiskitParser object.""" # pylint: disable = too-many-locals # ---------------------------------------------------------------------- # Punctuation marks @@ -899,7 +860,7 @@ def __init__(self): def parse_str(self, qasm_str): """ - Parse a QASM string + Parse a QASM string. Args: qasm_str (str): QASM string @@ -908,7 +869,7 @@ def parse_str(self, qasm_str): def parse_file(self, fname): """ - Parse a QASM file + Parse a QASM file. Args: fname (str): Filename @@ -917,16 +878,14 @@ def parse_file(self, fname): def _reset(): - """ - Reset internal variables - """ + """Reset internal variables.""" # pylint: disable = invalid-name, global-statement global _QISKIT_VARS, _BITS_VARS, _CUSTOM_GATES, _OPAQUE_GATES - _QISKIT_VARS = dict() - _BITS_VARS = dict() - _CUSTOM_GATES = dict() - _OPAQUE_GATES = dict() + _QISKIT_VARS = {} + _BITS_VARS = {} + _CUSTOM_GATES = {} + _OPAQUE_GATES = {} # ============================================================================== @@ -938,19 +897,16 @@ def _reset(): def read_qasm_str(eng, qasm_str): """ - Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to - ProjectQ commands. + Read an OpenQASM (2.0, 3.0 is experimental) string and convert it to ProjectQ commands. - This version of the function uses pyparsing in order to parse the *.qasm - file. + This version of the function uses pyparsing in order to parse the *.qasm file. Args: eng (MainEngine): MainEngine to use for creating qubits and commands. filename (string): Path to *.qasm file Note: - At this time, we support most of OpenQASM 2.0 and some of 3.0, - although the latter is still experimental. + At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ _reset() for operation in parser.parse_str(qasm_str).asList(): @@ -963,23 +919,19 @@ def read_qasm_str(eng, qasm_str): def read_qasm_file(eng, filename): """ - Read an OpenQASM (2.0, 3.0 is experimental) and convert it to ProjectQ - Commands. + Read an OpenQASM (2.0, 3.0 is experimental) and convert it to ProjectQ Commands. - This version of the function uses pyparsing in order to parse the *.qasm - file. + This version of the function uses pyparsing in order to parse the *.qasm file. Args: eng (MainEngine): MainEngine to use for creating qubits and commands. filename (string): Path to *.qasm file Note: - At this time, we support most of OpenQASM 2.0 and some of 3.0, - although the latter is still experimental. + At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. - Also note that we do not try to enforce 100% conformity to the - OpenQASM standard while parsing QASM code. The parser may allow some - syntax that are actually banned by the standard. + Also note that we do not try to enforce 100% conformity to the OpenQASM standard while parsing QASM code. The + parser may allow some syntax that are actually banned by the standard. """ _reset() for operation in parser.parse_file(filename).asList(): diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index e1c46e9b..95cc080a 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -14,18 +14,20 @@ # limitations under the License. import platform -import pytest import tempfile -from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate -from projectq.cengines import MainEngine, DummyEngine +import pytest + from projectq.backends import CommandPrinter +from projectq.cengines import DummyEngine, MainEngine +from projectq.ops import AllocateQubitGate, HGate, MeasureGate, SGate, TGate, XGate # ============================================================================== _has_pyparsing = True try: import pyparsing # noqa: F401 + from ._parse_qasm_pyparsing import read_qasm_file, read_qasm_str except ImportError: _has_pyparsing = False diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 685192e2..7db16180 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -12,9 +12,10 @@ # 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. -""" Define function to read OpenQASM file format (using Qiskit). """ -from qiskit.circuit import QuantumCircuit, Clbit +"""Function definitions to read OpenQASM file format (using Qiskit).""" + +from qiskit.circuit import Clbit, QuantumCircuit from projectq.ops import All, Measure @@ -145,7 +146,6 @@ def read_qasm_file(eng, filename): Note: At this time, we support most of OpenQASM 2.0 and some of 3.0, although the latter is still experimental. """ - circuit = QuantumCircuit.from_qasm_file(filename) return _convert_qiskit_circuit(eng, circuit) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index ab77872f..c527c155 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -14,18 +14,20 @@ # limitations under the License. import platform -import pytest import tempfile -from projectq.ops import AllocateQubitGate, XGate, MeasureGate, HGate, SGate, TGate -from projectq.cengines import MainEngine, DummyEngine +import pytest + from projectq.backends import CommandPrinter +from projectq.cengines import DummyEngine, MainEngine +from projectq.ops import AllocateQubitGate, HGate, MeasureGate, SGate, TGate, XGate # ============================================================================== _has_qiskit = True try: import qiskit # noqa: F401 + from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str except ImportError: _has_qiskit = False diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py index 61611f38..2db7f5aa 100644 --- a/projectq/libs/qasm/_pyparsing_expr.py +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -12,25 +12,24 @@ # 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. -""" -Helper module to parse expressions -""" + +"""Helper module to parse expressions.""" import math -from numbers import Number import operator +from numbers import Number from pyparsing import ( + CaselessKeyword, + Forward, + Group, Literal, + Regex, + Suppress, Word, - Group, - Forward, ZeroOrMore, - alphas, alphanums, - Regex, - CaselessKeyword, - Suppress, + alphas, delimitedList, pyparsing_common, ) @@ -66,7 +65,7 @@ def push_unary_minus(toks): class ExprParser: """ - Expression parser + Expression parser. Grammar: expop :: '^' @@ -80,8 +79,9 @@ class ExprParser: """ def __init__(self): + """Initialize an ExprParser object.""" # pylint: disable = too-many-locals - self.var_dict = dict() + self.var_dict = {} # use CaselessKeyword for e and pi, to avoid accidentally matching # functions that start with 'e' or 'pi' (such as 'exp'); Keyword @@ -149,8 +149,7 @@ def set_var_dict(self, var_dict): Set the internal variable dictionary. Args: - var_dict (dict): Dictionary of variables with their corresponding - value for substitution. + var_dict (dict): Dictionary of variables with their corresponding value for substitution. """ self.var_dict = var_dict @@ -158,8 +157,7 @@ def _eval_var_expr(self, toks): """ Evaluate an expression containing a variable. - Name matching keys in the internal variable dictionary have their - values substituted. + Name matching keys in the internal variable dictionary have their values substituted. Args: toks (pyparsing.Tokens): Pyparsing tokens @@ -265,6 +263,6 @@ def eval_expr(expr_str, var_dict=None): global EXPR_STACK EXPR_STACK = [] - _parser.set_var_dict(var_dict if var_dict else dict()) + _parser.set_var_dict(var_dict if var_dict else {}) _parser.parse(expr_str) return evaluate_stack(EXPR_STACK[:]) diff --git a/projectq/libs/qasm/_pyparsing_expr_test.py b/projectq/libs/qasm/_pyparsing_expr_test.py index cd91b4de..1358db7c 100644 --- a/projectq/libs/qasm/_pyparsing_expr_test.py +++ b/projectq/libs/qasm/_pyparsing_expr_test.py @@ -13,9 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import math +import pytest + from ._pyparsing_expr import eval_expr # ============================================================================== diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index ec0dcd5b..a5cacfd3 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -13,27 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains some helper variables for the Qiskit conversion functions""" +"""Definition of helper variables for the Qiskit conversion functions.""" from projectq.ops import ( + CNOT, + U2, + U3, Barrier, + C, HGate, - SGate, - Sdagger, - TGate, - Tdagger, - XGate, - YGate, - ZGate, Rx, Ry, Rz, + Sdagger, + SGate, SwapGate, + Tdagger, + TGate, Toffoli, - C, - CNOT, - U2, - U3, + XGate, + YGate, + ZGate, ) # ============================================================================== diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py index 8ffef3f6..1ff55beb 100644 --- a/projectq/libs/qasm/_utils.py +++ b/projectq/libs/qasm/_utils.py @@ -12,35 +12,31 @@ # 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. + """Some helper utilities.""" -from projectq.ops import ControlledGate, SwapGate, BasicGate +from projectq.ops import BasicGate, ControlledGate, SwapGate # ============================================================================== class OpaqueGate(BasicGate): - """ - Gate representing an opaque gate type. - """ + """Gate representing an opaque gate type.""" def __init__(self, name, params): """ - Constructor + Initialize an OpaqueGate object. Args: name (str): Name/type of the quantum gate params (list,tuple): Parameter for the gate (may be empty) """ - super().__init__() self.name = name self.params = params def __str__(self): - """ - String conversion. - """ + """Return the string representation of an OpaqueGate.""" # TODO: This is a bit crude... if self.params: return 'Opaque({})({})'.format(self.name, ','.join(self.params)) diff --git a/projectq/libs/qasm/_utils_test.py b/projectq/libs/qasm/_utils_test.py index 2beb9696..d4fda647 100644 --- a/projectq/libs/qasm/_utils_test.py +++ b/projectq/libs/qasm/_utils_test.py @@ -16,10 +16,31 @@ import pytest -from projectq.types import WeakQubitRef from projectq.cengines import DummyEngine -from projectq.ops import X, Y, Z, T, Tdagger, S, Sdagger, H, Ph, R, Rx, Ry, Rz, U2, U3, Swap, Toffoli, Barrier, C -from ._utils import apply_gate, OpaqueGate +from projectq.ops import ( + U2, + U3, + Barrier, + C, + H, + Ph, + R, + Rx, + Ry, + Rz, + S, + Sdagger, + Swap, + T, + Tdagger, + Toffoli, + X, + Y, + Z, +) +from projectq.types import WeakQubitRef + +from ._utils import OpaqueGate, apply_gate # ============================================================================== diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 2f644636..10e4077b 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -76,7 +76,6 @@ def _angle_to_str(angle, symbols): return f"({str(angle)})" - class BasicGate: """Base class of all gates. (Don't use it directly but derive from it).""" @@ -488,10 +487,7 @@ def tex_str(self): ) def get_inverse(self): - """ - Return the inverse of this rotation gate (negate the angle, return new - object). - """ + """Return the inverse of this rotation gate (negate the angle, return new object).""" if (self.theta, self.phi, self.lamda) == (0, 0, 0): return self.__class__(0, 0, 0) return self.__class__(-self.theta + 4 * math.pi, -self.phi + 4 * math.pi, -self.lamda + 4 * math.pi) @@ -523,16 +519,12 @@ def __eq__(self, other): return (self.theta, self.phi, self.lamda) == (other.theta, other.phi, other.lamda) return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def is_identity(self): - """ - Return True if the gate is equivalent to an Identity gate - """ + """Return True if the gate is equivalent to an Identity gate.""" return ( (self.theta == 0.0 or self.theta == 4 * math.pi) and (self.phi == 0.0 or self.phi == 4 * math.pi) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index a68d7996..d3f06e8c 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -311,13 +311,11 @@ def matrix(self): class U3(U3Gate): - """ - U3 rotation gate class - """ + """U3 rotation gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [ @@ -333,11 +331,16 @@ def matrix(self): class U2(U3): - """ - U2 rotation gate class - """ + """U2 rotation gate class.""" def __init__(self, phi, lamda): + """ + Initialize a U2 gate. + + Args: + phi (float): Angle of rotation (saved modulo 4 * pi) + lamda (float): Angle of rotation (saved modulo 4 * pi) + """ super().__init__(math.pi / 2, phi, lamda) From 9b475d56c54485094542ac46a0453bad9cc3164e Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 5 Jan 2022 21:55:16 +0100 Subject: [PATCH 37/42] Force pip update for some CI workflows --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c56b033d..23fa6f70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,7 +153,7 @@ jobs: - name: Prepare Python env run: | python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket,qiskit,pyparsing cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary @@ -204,7 +204,7 @@ jobs: - name: Prepare Python env run: | python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket,qiskit,pyparsing cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary From bcb21154ec9f364b4e093678f8f1c7f5a9ae39ee Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Sun, 30 Oct 2022 22:44:01 +0100 Subject: [PATCH 38/42] Fix issues due to last rebase --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23fa6f70..c9d429de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,10 +286,8 @@ jobs: - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - fi + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Cache wheels uses: actions/cache@v3 From a95f2d0fc30b2d8aab38db1c1ea7cdeb7fca55c9 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 31 Oct 2022 19:19:18 +0100 Subject: [PATCH 39/42] Fix build error linked to pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f3f3e0ae..e873b8c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dynamic = ["version"] dependencies = [ 'matplotlib >= 2.2.3', 'networkx >= 2', - 'numpy >= 1.17', # Mainly for Qiskit + 'numpy >= 1.17', 'requests', 'scipy' ] From cd9b4083bcb0c705a2796cd40b90e8cb3185cb72 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 31 Oct 2022 19:35:19 +0100 Subject: [PATCH 40/42] Fix pre-commit warnings --- .codespell.allow | 1 + .pre-commit-config.yaml | 2 +- projectq/backends/_qasm.py | 50 ++++++++---------- projectq/backends/_qasm_test.py | 9 ++-- projectq/libs/qasm/__init__.py | 14 ++--- projectq/libs/qasm/_parse_qasm_pyparsing.py | 51 +++++++++---------- .../libs/qasm/_parse_qasm_pyparsing_test.py | 1 - projectq/libs/qasm/_parse_qasm_qiskit.py | 3 +- projectq/libs/qasm/_parse_qasm_qiskit_test.py | 1 - projectq/libs/qasm/_pyparsing_expr.py | 1 - projectq/libs/qasm/_pyparsing_expr_test.py | 1 - projectq/libs/qasm/_qiskit_conv.py | 1 - projectq/libs/qasm/_utils.py | 5 +- projectq/libs/qasm/_utils_test.py | 51 +++++++++---------- projectq/ops/_basics_test.py | 2 +- pyproject.toml | 2 +- 16 files changed, 83 insertions(+), 112 deletions(-) diff --git a/.codespell.allow b/.codespell.allow index 1edf5ded..f5a46aab 100644 --- a/.codespell.allow +++ b/.codespell.allow @@ -3,3 +3,4 @@ braket te Ket ket +lamda diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e75878e6..04a177bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -117,7 +117,7 @@ repos: hooks: - id: pylint args: ['--score=n', '--disable=no-member'] - additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx, qiskit-terra, pyparsing] + additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx, pyparsing] - repo: https://github.com/mgedmin/check-manifest rev: '0.49' diff --git a/projectq/backends/_qasm.py b/projectq/backends/_qasm.py index 33811697..c1d92078 100644 --- a/projectq/backends/_qasm.py +++ b/projectq/backends/_qasm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,6 +11,7 @@ # 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. + """Backend to convert ProjectQ commands to OpenQASM.""" from copy import deepcopy @@ -57,7 +57,7 @@ def __init__( """ Initialize an OpenQASMBackend object. - Contrary to OpenQASM, ProjectQ does not impose the restriction that a programm must start with qubit/bit + Contrary to OpenQASM, ProjectQ does not impose the restriction that a program must start with qubit/bit allocations and end with some measurements. The user can configure what happens each time a FlushGate() is encountered by setting the `collate` and @@ -168,7 +168,7 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements n_controls = get_control_count(cmd) def _format_angle(angle): - return '({})'.format(angle) + return f'({angle})' _ccontrolled_gates_func = { X: 'ccx', @@ -223,8 +223,8 @@ def _format_angle(angle): self._creg_dict[qb_id] = self._gen_bit_name(index) if add: - self._output.append('qubit {};'.format(self._qreg_dict[qb_id])) - self._output.append('bit {};'.format(self._creg_dict[qb_id])) + self._output.append(f'qubit {self._qreg_dict[qb_id]};') + self._output.append(f'bit {self._creg_dict[qb_id]};') elif gate == Deallocate: qb_id = cmd.qubits[0][0].id @@ -238,16 +238,16 @@ def _format_angle(angle): assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 qb_id = cmd.qubits[0][0].id - self._output.append('{} = measure {};'.format(self._creg_dict[qb_id], self._qreg_dict[qb_id])) + self._output.append(f'{self._creg_dict[qb_id]} = measure {self._qreg_dict[qb_id]};') elif n_controls == 2: targets = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] controls = [self._qreg_dict[qb.id] for qb in cmd.control_qubits] try: - self._output.append('{} {};'.format(_ccontrolled_gates_func[gate], ','.join(controls + targets))) + self._output.append(f'{_ccontrolled_gates_func[gate]} {",".join(controls + targets)};') except KeyError as err: - raise RuntimeError('Unable to perform {} gate with n=2 control qubits'.format(gate)) from err + raise RuntimeError(f'Unable to perform {gate} gate with n=2 control qubits') from err elif n_controls == 1: target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] @@ -255,12 +255,8 @@ def _format_angle(angle): try: if isinstance(gate, Ph): self._output.append( - '{}{} {},{};'.format( - _controlled_gates_func[type(gate)], - _format_angle(-gate.angle / 2.0), - self._qreg_dict[cmd.control_qubits[0].id], - target_qureg[0], - ) + f'{_controlled_gates_func[type(gate)]}{_format_angle(-gate.angle / 2.0)} ' + f'{self._qreg_dict[cmd.control_qubits[0].id]},{target_qureg[0]};' ) elif isinstance( gate, @@ -270,33 +266,27 @@ def _format_angle(angle): ), ): self._output.append( - '{}{} {},{};'.format( - _controlled_gates_func[type(gate)], - _format_angle(gate.angle), - self._qreg_dict[cmd.control_qubits[0].id], - target_qureg[0], - ) + f'{_controlled_gates_func[type(gate)]}{_format_angle(gate.angle)} ' + f'{self._qreg_dict[cmd.control_qubits[0].id]},{target_qureg[0]};' ) else: self._output.append( - '{} {},{};'.format( + '{} {},{};'.format( # pylint: disable=consider-using-f-string _controlled_gates_func[gate], self._qreg_dict[cmd.control_qubits[0].id], *target_qureg ) ) except KeyError as err: - raise RuntimeError('Unable to perform {} gate with n=1 control qubits'.format(gate)) from err + raise RuntimeError(f'Unable to perform {gate} gate with n=1 control qubits') from err else: target_qureg = [self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg] if isinstance(gate, Ph): - self._output.append( - '{}{} {};'.format(_gates_func[type(gate)], _format_angle(-gate.angle / 2.0), target_qureg[0]) - ) + self._output.append(f'{_gates_func[type(gate)]}{_format_angle(-gate.angle / 2.0)} {target_qureg[0]};') elif isinstance(gate, (R, Rx, Ry, Rz)): + self._output.append(f'{_gates_func[type(gate)]}{_format_angle(gate.angle)} {target_qureg[0]};') + else: self._output.append( - '{}{} {};'.format(_gates_func[type(gate)], _format_angle(gate.angle), target_qureg[0]) + '{} {};'.format(_gates_func[gate], *target_qureg) # pylint: disable=consider-using-f-string ) - else: - self._output.append('{} {};'.format(_gates_func[gate], *target_qureg)) def _insert_openqasm_header(self): self._output.append('OPENQASM 3;') @@ -311,6 +301,6 @@ def _reset_after_flush(self): self._output.clear() self._insert_openqasm_header() for qubit_name in self._qreg_dict.values(): - self._output.append('qubit {};'.format(qubit_name)) + self._output.append(f'qubit {qubit_name};') for bit_name in self._creg_dict.values(): - self._output.append('bit {};'.format(bit_name)) + self._output.append(f'bit {bit_name};') diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index 65ccd385..ecda8032 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,8 +84,8 @@ def test_qasm_allocate_deallocate(qubit_id_redux): assert not backend._available_indices qasm = '\n'.join(eng.backend.qasm) for i in range(1, 6): - assert re.search(r'qubit\s+q{}'.format(i), qasm) - assert re.search(r'bit\s+c{}'.format(i), qasm) + assert re.search(fr'qubit\s+q{i}', qasm) + assert re.search(fr'bit\s+c{i}', qasm) del qubit eng.flush() @@ -394,10 +393,10 @@ def _process(output): def test_qasm_name_callback(): def _qubit(index): - return 'qubit_{}'.format(index) + return f'qubit_{index}' def _bit(index): - return 'classical_bit_{}'.format(index) + return f'classical_bit_{index}' eng = MainEngine(backend=OpenQASMBackend(qubit_callback=_qubit, bit_callback=_bit), engine_list=[]) diff --git a/projectq/libs/qasm/__init__.py b/projectq/libs/qasm/__init__.py index 0db8b575..6efdffdc 100644 --- a/projectq/libs/qasm/__init__.py +++ b/projectq/libs/qasm/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,9 +11,8 @@ # 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. -""" -Contains functions/classes to handle OpenQASM -""" + +"""Contains functions/classes to handle OpenQASM.""" try: from ._parse_qasm_qiskit import read_qasm_file, read_qasm_str @@ -28,15 +26,11 @@ ) def read_qasm_file(eng, filename): - """ - Dummy implementation - """ + """Invalid implementation.""" # pylint: disable=unused-argument raise RuntimeError(ERROR_MSG) def read_qasm_str(eng, qasm_str): - """ - Dummy implementation - """ + """Invalid implementation.""" # pylint: disable=unused-argument raise RuntimeError(ERROR_MSG) diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing.py b/projectq/libs/qasm/_parse_qasm_pyparsing.py index c94ba19a..8135793f 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +95,7 @@ def eval(self, _): def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return 'QASMVersionOp({})'.format(self.version) + return f'QASMVersionOp({self.version})' class IncludeOp: @@ -121,11 +120,11 @@ def eval(self, _): if self.fname in 'qelib1.inc, stdlib.inc': pass else: # pragma: nocover - raise RuntimeError('Invalid cannot read: {}! (unsupported)'.format(self.fname)) + raise RuntimeError(f'Invalid cannot read: {self.fname}! (unsupported)') def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return 'IncludeOp({})'.format(self.fname) + return f'IncludeOp({self.fname})' # ============================================================================== @@ -162,8 +161,8 @@ def eval(self, _): def __repr__(self): # pragma: nocover """Mainly for debugging.""" if self.index is not None: - return 'Qubit({}[{}])'.format(self.name, self.index) - return 'Qubit({})'.format(self.name) + return f'Qubit({self.name}[{self.index}])' + return f'Qubit({self.name})' # ============================================================================== @@ -192,9 +191,9 @@ def __init__(self, type_t, nbits, name, init): def __repr__(self): # pragma: nocover """Mainly for debugging.""" if self.init: - return "{}({}, {}, {}) = {}".format(self.__class__.__name__, self.type_t, self.nbits, self.name, self.init) + return f"{self.__class__.__name__}({self.type_t}, {self.nbits}, {self.name}) = {self.init}" - return "{}({}, {}, {})".format(self.__class__.__name__, self.type_t, self.nbits, self.name) + return f"{self.__class__.__name__}({self.type_t}, {self.nbits}, {self.name})" # ------------------------------------------------------------------------------ @@ -215,7 +214,7 @@ def eval(self, eng): if self.name not in _QISKIT_VARS: _QISKIT_VARS[self.name] = eng.allocate_qureg(self.nbits) else: # pragma: nocover - raise RuntimeError('Variable exist already: {}'.format(self.name)) + raise RuntimeError(f'Variable exist already: {self.name}') # ------------------------------------------------------------------------------ @@ -240,7 +239,7 @@ def eval(self, _): if self.init: # pragma: nocover init = parse_expr(self.init) - # The followings are OpenQASM 3.0 + # The following are OpenQASM 3.0 if self.type_t in ('const', 'float', 'fixed', 'angle'): # pragma: nocover _BITS_VARS[self.name] = float(init) elif self.type_t in ('int', 'uint'): # pragma: nocover @@ -252,7 +251,7 @@ def eval(self, _): assert self.init is None _BITS_VARS[self.name] = [False] * self.nbits else: # pragma: nocover - raise RuntimeError('Variable exist already: {}'.format(self.name)) + raise RuntimeError(f'Variable exist already: {self.name}') # ============================================================================== @@ -286,7 +285,7 @@ def eval(self, _): def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return "GateDefOp({}, {}, {})\n\t{}".format(self.name, self.params, self.qparams, self.body) + return f"GateDefOp({self.name}, {self.params}, {self.qparams})\n\t{self.body}" # ============================================================================== @@ -323,8 +322,6 @@ def eval(self, eng): # pylint: disable = pointless-statement, expression-not-assigned # pylint: disable = global-statement - global _BITS_VARS - qubits = self.qubits.eval(eng) if not isinstance(qubits, list): Measure | qubits @@ -345,7 +342,7 @@ def eval(self, eng): def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return 'MeasureOp({}, {})'.format(self.qubits, self.bits) + return f'MeasureOp({self.qubits}, {self.bits})' # ------------------------------------------------------------------------------ @@ -378,8 +375,8 @@ def eval(self, _): def __repr__(self): # pragma: nocover """Mainly for debugging.""" if self.params: - return 'OpaqueOp({}, {})'.format(self.name, self.params) - return 'OpaqueOp({})'.format(self.name) + return f'OpaqueOp({self.name}, {self.params})' + return f'OpaqueOp({self.name})' # ------------------------------------------------------------------------------ @@ -388,7 +385,7 @@ def __repr__(self): # pragma: nocover class GateOp: """Gate applied to qubits operation.""" - def __init__(self, s, loc, toks): + def __init__(self, string, loc, toks): """ Initialize a GateOp object. @@ -401,7 +398,7 @@ def __init__(self, s, loc, toks): self.params = [] self.qubits = [QubitProxy(qubit) for qubit in toks[1]] else: - param_str = s[loc : s.find(';', loc)] # noqa: E203 + param_str = string[loc : string.find(';', loc)] # noqa: E203 self.params = param_str[param_str.find('(') + 1 : param_str.rfind(')')].split(',') # noqa: E203 self.qubits = [QubitProxy(qubit) for qubit in toks[2]] @@ -457,14 +454,14 @@ def eval(self, eng): # pylint: disable=too-many-branches _BITS_VARS = bits_vars_bak else: # pragma: nocover if self.params: - gate_str = '{}({}) | {}'.format(self.name, self.params, self.qubits) + gate_str = f'{self.name}({self.params}) | {self.qubits}' else: - gate_str = '{} | {}'.format(self.name, self.qubits) - raise RuntimeError('Unknown gate: {}'.format(gate_str)) + gate_str = f'{self.name} | {self.qubits}' + raise RuntimeError(f'Unknown gate: {gate_str}') def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return 'GateOp({}, {}, {})'.format(self.name, self.params, self.qubits) + return f'GateOp({self.name}, {self.params}, {self.qubits})' # ============================================================================== @@ -496,12 +493,12 @@ def eval(self, _): value = parse_expr(self.value) _BITS_VARS[self.var] = value else: - raise RuntimeError('The variable {} is not defined!'.format(self.var)) + raise RuntimeError(f'The variable {self.var} is not defined!') return 0 def __repr__(self): """Mainly for debugging.""" - return 'AssignOp({},{})'.format(self.var, self.value) + return f'AssignOp({self.var},{self.value})' # ============================================================================== @@ -520,7 +517,7 @@ def _parse_if_conditional(if_str): level -= 1 if level == 0: return if_str[start : start + idx] # noqa: E203 - raise RuntimeError('Unbalanced parantheses in {}'.format(if_str)) # pragma: nocover + raise RuntimeError(f'Unbalanced parentheses in {if_str}') # pragma: nocover class IfOp: @@ -584,7 +581,7 @@ def eval(self, eng): def __repr__(self): # pragma: nocover """Mainly for debugging.""" - return "IfExpr({} {} {}) {{ {} }}".format(self.bit, self.binary_op, self.comp_expr, self.body) + return f"IfExpr({self.bit} {self.binary_op} {self.comp_expr}) {{ {self.body} }}" # ============================================================================== diff --git a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py index 95cc080a..abf7f126 100644 --- a/projectq/libs/qasm/_parse_qasm_pyparsing_test.py +++ b/projectq/libs/qasm/_parse_qasm_pyparsing_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 7db16180..249745f4 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +40,7 @@ def apply_op(eng, gate, qubits, bits, bits_map): # pylint: disable = expression-not-assigned, protected-access if bits: - # Only measurement gates have clasical bits + # Only measurement gates have classical bits assert len(qubits) == len(bits) All(Measure) | qubits eng.flush() diff --git a/projectq/libs/qasm/_parse_qasm_qiskit_test.py b/projectq/libs/qasm/_parse_qasm_qiskit_test.py index c527c155..faff7d6d 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit_test.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/qasm/_pyparsing_expr.py b/projectq/libs/qasm/_pyparsing_expr.py index 2db7f5aa..c015aa21 100644 --- a/projectq/libs/qasm/_pyparsing_expr.py +++ b/projectq/libs/qasm/_pyparsing_expr.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/qasm/_pyparsing_expr_test.py b/projectq/libs/qasm/_pyparsing_expr_test.py index 1358db7c..9072e60d 100644 --- a/projectq/libs/qasm/_pyparsing_expr_test.py +++ b/projectq/libs/qasm/_pyparsing_expr_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index a5cacfd3..5ba7f3a3 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/qasm/_utils.py b/projectq/libs/qasm/_utils.py index 1ff55beb..4929e6ec 100644 --- a/projectq/libs/qasm/_utils.py +++ b/projectq/libs/qasm/_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,8 +38,8 @@ def __str__(self): """Return the string representation of an OpaqueGate.""" # TODO: This is a bit crude... if self.params: - return 'Opaque({})({})'.format(self.name, ','.join(self.params)) - return 'Opaque({})'.format(self.name) + return f'Opaque({self.name})({",".join(self.params)})' + return f'Opaque({self.name})' # ============================================================================== diff --git a/projectq/libs/qasm/_utils_test.py b/projectq/libs/qasm/_utils_test.py index d4fda647..8ea16c5f 100644 --- a/projectq/libs/qasm/_utils_test.py +++ b/projectq/libs/qasm/_utils_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -64,32 +63,30 @@ def test_opaque_gate(): @pytest.mark.parametrize( 'gate, n_qubits', ( - list( - map( - lambda x: (x, 1), - [ - X, - Y, - Z, - S, - Sdagger, - T, - Tdagger, - H, - Barrier, - Ph(1.12), - Rx(1.12), - Ry(1.12), - Rz(1.12), - R(1.12), - U2(1.12, 1.12), - U3(1.12, 1.12, 1.12), - ], - ) - ) - + list(map(lambda x: (x, 2), [C(X), C(Y), C(Z), Swap, Barrier])) - + list(map(lambda x: (x, 3), [Toffoli, C(Swap), Barrier])) - + list(map(lambda x: (x, 10), [Barrier])) + [ + (x, 1) + for x in [ + X, + Y, + Z, + S, + Sdagger, + T, + Tdagger, + H, + Barrier, + Ph(1.12), + Rx(1.12), + Ry(1.12), + Rz(1.12), + R(1.12), + U2(1.12, 1.12), + U3(1.12, 1.12, 1.12), + ] + ] + + [(x, 2) for x in [C(X), C(Y), C(Z), Swap, Barrier]] + + [(x, 3) for x in [Toffoli, C(Swap), Barrier]] + + [(x, 10) for x in [Barrier]] ), ids=str, ) diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index f098e7fd..e06a7382 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -250,7 +250,7 @@ def test_u3_gate_init(input_angle, modulo_angle): def test_u3_gate_str(): gate = _basics.U3Gate(math.pi, math.pi, math.pi) assert str(gate) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)" - assert gate.to_string(symbols=True) == u"U3Gate(1.0π,1.0π,1.0π)" + assert gate.to_string(symbols=True) == "U3Gate(1.0π,1.0π,1.0π)" assert gate.to_string(symbols=False) == "U3Gate(3.14159265359,3.14159265359,3.14159265359)" diff --git a/pyproject.toml b/pyproject.toml index e873b8c1..0a1a4ba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -173,7 +173,7 @@ ignore = [ ] [tool.pylint.typecheck] - ignored-modules = ['boto3', 'botocore', 'sympy'] + ignored-modules = ['boto3', 'botocore', 'sympy', 'qiskit'] [tool.pytest.ini_options] From 5965506ad0f0d9cae1db4504d7bda2e80d967c98 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 2 Apr 2024 22:50:25 +0200 Subject: [PATCH 41/42] Fix linter issues --- projectq/backends/_qasm_test.py | 6 +++--- projectq/libs/qasm/_qiskit_conv.py | 4 ++-- projectq/ops/_basics.py | 24 +++++++++++++----------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/projectq/backends/_qasm_test.py b/projectq/backends/_qasm_test.py index ecda8032..f2eb904c 100644 --- a/projectq/backends/_qasm_test.py +++ b/projectq/backends/_qasm_test.py @@ -137,7 +137,7 @@ def test_qasm_allocate_deallocate(qubit_id_redux): (Barrier, True), (Entangle, False), ], - ids=lambda l: str(l), + ids=lambda lamda: str(lamda), ) def test_qasm_is_available(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) @@ -164,7 +164,7 @@ def test_qasm_is_available(gate, is_available): (Rx(0.5), False), (Ry(0.5), False), ], - ids=lambda l: str(l), + ids=lambda lamda: str(lamda), ) def test_qasm_is_available_1control(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) @@ -192,7 +192,7 @@ def test_qasm_is_available_1control(gate, is_available): (Rx(0.5), False), (Ry(0.5), False), ], - ids=lambda l: str(l), + ids=lambda lamda: str(lamda), ) def test_qasm_is_available_2control(gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[OpenQASMBackend()]) diff --git a/projectq/libs/qasm/_qiskit_conv.py b/projectq/libs/qasm/_qiskit_conv.py index 5ba7f3a3..eec9abd2 100644 --- a/projectq/libs/qasm/_qiskit_conv.py +++ b/projectq/libs/qasm/_qiskit_conv.py @@ -64,8 +64,8 @@ 'cswap': lambda: C(SwapGate()), 'crz': lambda a: C(Rz(a)), 'cu1': lambda a: C(Rz(a)), - 'cu2': lambda p, l: C(U2(p, l)), - 'cu3': lambda t, p, l: C(U3(t, p, l)), + 'cu2': lambda phi, lamda: C(U2(phi, lamda)), + 'cu3': lambda theta, phi, lamda: C(U3(theta, phi, lamda)), # Doubly-controlled gates "ccx": lambda: Toffoli, } diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 10e4077b..861dcfd8 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -72,8 +72,8 @@ def _round_angle(angle, mod_pi): def _angle_to_str(angle, symbols): if symbols: - return f"({str(round(angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')})" - return f"({str(angle)})" + return f"{str(round(angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')}" + return f"{str(angle)}" class BasicGate: @@ -468,8 +468,11 @@ def to_string(self, symbols=False): more user friendly display if True, full angle written in radian otherwise. """ - return str(self.__class__.__name__) + '({},{},{})'.format( - _angle_to_str(self.theta, symbols), _angle_to_str(self.phi, symbols), _angle_to_str(self.lamda, symbols) + return ( + str(self.__class__.__name__) + + f'({_angle_to_str(self.theta, symbols)},' + + f'{_angle_to_str(self.phi, symbols)},' + + f'{_angle_to_str(self.lamda, symbols)})' ) def tex_str(self): @@ -482,8 +485,11 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$({}\\pi,{}\\pi,{}\\pi)$".format( - round(self.theta / math.pi, 3), round(self.phi / math.pi, 3), round(self.lamda / math.pi, 3) + return ( + str(self.__class__.__name__) + + f'$({round(self.theta / math.pi, 3)}\\pi,' + + f'{round(self.phi / math.pi, 3)}\\pi,' + + f'{round(self.lamda / math.pi, 3)}\\pi)$' ) def get_inverse(self): @@ -525,11 +531,7 @@ def __hash__(self): def is_identity(self): """Return True if the gate is equivalent to an Identity gate.""" - return ( - (self.theta == 0.0 or self.theta == 4 * math.pi) - and (self.phi == 0.0 or self.phi == 4 * math.pi) - and (self.lamda == 0.0 or self.lamda == 4 * math.pi) - ) + return self.theta in (0.0, 4 * math.pi) and self.phi in (0.0, 4 * math.pi) and self.lamda in (0.0, 4 * math.pi) class BasicPhaseGate(BasicGate): From 6c64da4d6e951f9ac69c09c68f1212a7b6840946 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 2 Apr 2024 23:24:36 +0200 Subject: [PATCH 42/42] Fix tests and QASM parsing for custom gates --- projectq/libs/qasm/_parse_qasm_qiskit.py | 10 ++++++---- pyproject.toml | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/projectq/libs/qasm/_parse_qasm_qiskit.py b/projectq/libs/qasm/_parse_qasm_qiskit.py index 249745f4..9da90a0d 100644 --- a/projectq/libs/qasm/_parse_qasm_qiskit.py +++ b/projectq/libs/qasm/_parse_qasm_qiskit.py @@ -54,13 +54,15 @@ def apply_op(eng, gate, qubits, bits, bits_map): # TODO: This will silently discard opaque gates... return - gate_args = {gate.definition.qregs[0].name: qubits} - - for gate_sub, quregs_sub, bits_sub in gate.definition.data: + for gate_sub, quregs_sub, bits_sub in gate.definition._data: # OpenQASM 2.0 limitation... assert gate.name != 'measure' and not bits_sub apply_op( - eng, gate_sub, [gate_args[qubit.register.name][qubit.index] for qubit in quregs_sub], [], bits_map + eng, + gate_sub, + [qubits[gate.definition._qubit_indices[qubit].index] for qubit in quregs_sub], + [], + bits_map, ) else: gate_projectq = gates_conv_table[gate.name](*gate.params) diff --git a/pyproject.toml b/pyproject.toml index 0a1a4ba6..3c1897a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,11 +52,11 @@ braket = [ ] qiskit = [ - 'qiskit-terra>=0.15' + 'qiskit-terra>=0.17' ] qasm = [ - 'qiskit-terra>=0.15' + 'qiskit-terra>=0.17' ] pyparsing = [