From 5026302982946ca00f06bd7880efcd2d1b426591 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Mon, 8 Feb 2021 14:48:10 +0000 Subject: [PATCH 01/16] Update to add commutation rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update to add commutation rules to the optimizer engine. 1. Update _optimizer.py and _optimizer_test.py - apply_commutation Boolean – tells LocalOptimizer whether to consider commutation rules - Rearranged existing code to streamline and improve readability. - Restructuring of optimizer to check if the next command is commutable with the current command, or if it may be part of a commutable circuit. 2. Update _basics.py and _basics_test.py - Added attributes to do with commutation rules. 3. Update _command.py and _command_test.py - Added commutation attributes and overlap function to streamline optimizer code. 4. Update _gates.py and _gates_test.py - Added attributes to do with commutation rules. - Updated Rxx, Ryy, Rzz to have interchangeable qubit indices. 5. Update _metagates.py - Added commutable rule. 6. Addition of _relative_command.py and _relative_command_test.py - Dummy command which can be used without an engine, used to define commutation rules. 7. Update restricted_gate_set.py - By default set apply_commutation = True --- projectq/cengines/_optimize.py | 434 ++++++++++++++---- projectq/cengines/_optimize_test.py | 590 ++++++++++++++++++++++++- projectq/ops/_basics.py | 18 +- projectq/ops/_basics_test.py | 19 +- projectq/ops/_command.py | 55 ++- projectq/ops/_command_test.py | 49 +- projectq/ops/_gates.py | 56 ++- projectq/ops/_gates_test.py | 189 +++++--- projectq/ops/_metagates.py | 10 +- projectq/ops/_relative_command.py | 69 +++ projectq/ops/_relative_command_test.py | 18 + projectq/setups/restrictedgateset.py | 17 +- 12 files changed, 1335 insertions(+), 189 deletions(-) create mode 100644 projectq/ops/_relative_command.py create mode 100644 projectq/ops/_relative_command_test.py diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 0c9765288..9029eb19c 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -18,25 +18,23 @@ from copy import deepcopy as _deepcopy from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import FlushGate, FastForwardingGate, NotMergeable +from projectq.ops import FlushGate, FastForwardingGate, NotMergeable, XGate class LocalOptimizer(BasicEngine): """ - LocalOptimizer is a compiler engine which optimizes locally (merging + LocalOptimizer is a compiler engine which optimizes locally (e.g. merging rotations, cancelling gates with their inverse) in a local window of user- defined size. - It stores all commands in a dict of lists, where each qubit has its own gate pipeline. After adding a gate, it tries to merge / cancel successive gates using the get_merged and get_inverse functions of the gate (if available). For examples, see BasicRotationGate. Once a list corresponding to a qubit contains >=m gates, the pipeline is sent on to the next engine. """ - def __init__(self, m=5): + def __init__(self, m=5, apply_commutation=True): """ Initialize a LocalOptimizer object. - Args: m (int): Number of gates to cache per qubit, before sending on the first gate. @@ -44,18 +42,19 @@ def __init__(self, m=5): BasicEngine.__init__(self) self._l = dict() # dict of lists containing operations for each qubit self._m = m # wait for m gates before sending on + self._apply_commutation = apply_commutation + self._i_x_com = False - # sends n gate operations of the qubit with index idx def _send_qubit_pipeline(self, idx, n): """ Send n gate operations of the qubit with index idx to the next engine. """ - il = self._l[idx] # temporary label for readability - for i in range(min(n, len(il))): # loop over first n operations - # send all gates before n-qubit gate for other qubits involved + cmd_list = self._l[idx] # command list for qubit idx + for i in range(min(n, len(cmd_list))): # loop over first n commands + # send all gates before nth gate for other qubits involved # --> recursively call send_helper other_involved_qubits = [qb - for qreg in il[i].all_qubits + for qreg in cmd_list[i].all_qubits for qb in qreg if qb.id != idx] for qb in other_involved_qubits: @@ -63,14 +62,13 @@ def _send_qubit_pipeline(self, idx, n): try: gateloc = 0 # find location of this gate within its list - while self._l[Id][gateloc] != il[i]: + while self._l[Id][gateloc] != cmd_list[i]: gateloc += 1 gateloc = self._optimize(Id, gateloc) - # flush the gates before the n-qubit gate self._send_qubit_pipeline(Id, gateloc) - # delete the n-qubit gate, we're taking care of it + # delete the nth gate, we're taking care of it # and don't want the other qubit to do so self._l[Id] = self._l[Id][1:] except IndexError: @@ -79,7 +77,7 @@ def _send_qubit_pipeline(self, idx, n): # all qubits that need to be flushed have been flushed # --> send on the n-qubit gate - self.send([il[i]]) + self.send([cmd_list[i]]) # n operations have been sent on --> resize our gate list self._l[idx] = self._l[idx][n:] @@ -87,7 +85,6 @@ def _get_gate_indices(self, idx, i, IDs): """ Return all indices of a command, each index corresponding to the command's index in one of the qubits' command lists. - Args: idx (int): qubit index i (int): command position in qubit idx's command list @@ -114,88 +111,365 @@ def _get_gate_indices(self, idx, i, IDs): indices.append(identical_indices[num_identical_to_skip]) return indices + def _delete_command(self, idx, command_idx): + """ + Deletes the command at self._l[idx][command_idx] accounting + for all qubits in the optimizer dictionary. + """ + # List of the indices of the qubits that are involved + # in command + qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits + for qb in sublist] + # List of the command indices corresponding to the position + # of this command on each qubit id + commandidcs = self._get_gate_indices(idx, command_idx, qubitids) + for j in range(len(qubitids)): + try: + new_list = (self._l[qubitids[j]][0:commandidcs[j]] + + self._l[qubitids[j]][commandidcs[j]+1:]) + except: + # If there are no more commands after that being deleted. + new_list = (self._l[qubitids[j]][0:commandidcs[j]]) + self._l[qubitids[j]] = new_list + + def _replace_command(self, idx, command_idx, new_command): + """ + Replaces the command at self._l[idx][command_idx] accounting + for all qubits in the optimizer dictionary. + """ + # List of the indices of the qubits that are involved + # in command + qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits + for qb in sublist] + # List of the command indices corresponding to the position + # of this command on each qubit id + commandidcs = self._get_gate_indices(idx, command_idx, qubitids) + for j in range(len(qubitids)): + try: + new_list = (self._l[qubitids[j]][0:commandidcs[j]] + + [new_command] + + self._l[qubitids[j]][commandidcs[j]+1:]) + except: + # If there are no more commands after that being replaced. + new_list = (self._l[qubitids[j]][0:commandidcs[j]] + [new_command]) + self._l[qubitids[j]] = new_list + + def _get_erase_boolean(self, idx, qubitids, commandidcs, inverse_command, apply_commutation): + """ + Determines whether inverse commands should be cancelled + with one another. i.e. the commands between the pair are all + commutable for each qubit involved in the command. + """ + erase = True + # We dont want to examine qubit idx because the optimizer + # has already checked that the gates between the current + # and mergeable gates are commutable (or a commutable list). + commandidcs.pop(qubitids.index(idx)) # Remove corresponding + # position of command for qubit idx from commandidcs + qubitids.remove(idx) # Remove qubitid representing the current + # qubit in optimizer + x=1 + for j in range(len(qubitids)): + # Check that any gates between current gate and inverse + # gate are all commutable + this_command = self._l[qubitids[j]][commandidcs[j]] + future_command = self._l[qubitids[j]][commandidcs[j]+x] + while (future_command!=inverse_command): + if apply_commutation==False: + # If apply_commutation turned off, you should + # only get erase=True if commands are next to + # eachother on all qubits. i.e. if future_command + # and inverse_command are not equal (i.e. there + # are gates separating them), you don't want + # optimizer to look at whether the separating gates + # are commutable. + return False + if (this_command.is_commutable(future_command)==1): + x+=1 + future_command = self._l[qubitids[j]][commandidcs[j]+x] + erase = True + else: + erase = False + break + if (this_command.is_commutable(future_command)==2): + new_x = self._check_for_commutable_circuit(this_command, future_command, qubitids[j], commandidcs[j], 0) + if(new_x>x): + x=new_x + future_command = self._l[qubitids[j]][commandidcs[j]+x] + erase=True + else: + erase=False + break + return erase + + def _get_merge_boolean(self, idx, qubitids, commandidcs, merged_command, apply_commutation): + """ + To determine whether mergeable commands should be merged + with one another. i.e. the commands between them are all + commutable, for each qubit involved in the command. It does + not check for the situation where commands are separated by + a commutable list. However other parts of the optimizer + should find this situation. + """ + merge = True + # We dont want to examine qubit idx because the optimizer has already + # checked that the gates between the current and mergeable gates are + # commutable (or a commutable list). + commandidcs.pop(qubitids.index(idx)) # Remove corresponding position of command for qubit idx from commandidcs + qubitids.remove(idx) # Remove qubitid representing the current qubit in optimizer + for j in range(len(qubitids)): + # Check that any gates between current gate and mergeable + # gate are commutable + this_command = self._l[qubitids[j]][commandidcs[j]] + possible_command = None + merge = True + x=1 + while (possible_command!=merged_command): + if not apply_commutation: + # If apply_commutation turned off, you should + # only get erase=True if commands are next to + # eachother on all qubits. + return False + future_command = self._l[qubitids[j]][commandidcs[j]+x] + try: + possible_command = this_command.get_merged(future_command) + except: + pass + if (possible_command==merged_command): + merge = True + break + if (this_command.is_commutable(future_command)==1): + x+=1 + merge = True + continue + else: + merge = False + break + return merge + + def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): + """ command_i = current command + next_command = the next command + idx = index of the current qubit in the optimizer + i = index of the current command in the optimizer + x = number of commutable gates infront of i we have found + (if there is a commutable circuit, we pretend we have found + x commutable gates where x is the length of the commutable circuit """ + # commutable_circuit_list is a temp variable just used to create relative_commutable_circuits + commutable_circuit_list = command_i.gate.get_commutable_circuit_list(n=len(command_i._control_qubits), ) + relative_commutable_circuits = [] + # Keep a list of circuits that start with + # next_command. + for relative_circuit in commutable_circuit_list: + if (relative_circuit[0].gate.__class__ == next_command.gate.__class__): + relative_commutable_circuits.append(relative_circuit) + # Create dictionaries { absolute_qubit_idx : relative_qubit_idx } + # For the purposes of fast lookup, also { relative_qubit_idx : absolute_qubit_idx } + abs_to_rel = { idx : 0 } + rel_to_abs = { 0 : idx } + # If the current command is a CNOT, we set the target qubit idx + # to 0 + if command_i.gate.__class__==XGate: + if len(command_i._control_qubits)==1: + # At this point we know we have a CNOT + # we reset the dictionaries so that the + # target qubit in the abs dictionary + # corresponds to the target qubit in the + # rel dictionary + abs_to_rel = {command_i.qubits[0][0].id : 0} + rel_to_abs = {0 : command_i.qubits[0][0].id} + y=0 + absolute_circuit = self._l[idx][i+x+1:] + # If no (more) relative commutable circuits to check against, + # break out of this while loop and move on to next command_i. + while(len(relative_commutable_circuits)>0): + # If all the viable relative_circuits have been deleted + # you want to just move on + relative_circuit = relative_commutable_circuits[0] + while (y(len(absolute_circuit)-1)): + # The absolute circuit is too short to match the relative_circuit + # i.e. if the absolute circuit is of len=3, you can't have absolute_circuit[3] + # only absolute_circuit[0] - absolute_circuit[2] + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + # Check if relative_circuit command + # matches the absolute_circuit command + next_command = absolute_circuit[y] + if not (relative_circuit[y]._gate.__class__==next_command.gate.__class__): + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + + # Now we know the gates are equal. + # We check the idcs don't contradict our dictionaries. + # remember next_command = absolute_circuit[y]. + for qubit in next_command.qubits: + # We know a and r should correspond in both dictionaries. + a=qubit[0].id + r=relative_circuit[y].relative_qubit_idcs[0] + if a in abs_to_rel.keys(): + # If a in abs_to_rel, r will be in rel_to_abs + if (abs_to_rel[a] != r): + # Put it in a try block because pop will fail + # if relative_commutable_circuits already empty. + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + if r in rel_to_abs.keys(): + if (rel_to_abs[r] != a): + # Put it in a try block because pop will fail + # if relative_commutable_circuits already empty. + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + abs_to_rel[a] = r + rel_to_abs[r] = a + if(len(relative_commutable_circuits)==0): + break + # HERE: we know the qubit idcs don't contradict our dictionaries. + for ctrl_qubit in next_command.control_qubits: + # We know a and r should correspond in both dictionaries. + a=ctrl_qubit.id + r=relative_circuit[y].relative_ctrl_idcs[0] + if a in abs_to_rel.keys(): + # If a in abs_to_rel, r will be in rel_to_abs + if (abs_to_rel[a] != r): + # Put it in a try block because pop will fail + # if relative_commutable_circuits already empty. + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + if r in rel_to_abs.keys(): + if (rel_to_abs[r] != a): + # Put it in a try block because pop will fail + # if relative_commutable_circuits already empty. + if (len(relative_commutable_circuits)!=0): + relative_commutable_circuits.pop(0) + break + abs_to_rel[a] = r + rel_to_abs[r] = a + if(len(relative_commutable_circuits)==0): + break + # HERE: we know all relative/absolute qubits/ctrl qubits do not + # contradict dictionaries and are assigned. + y+=1 + if (y==len(relative_circuit)): + # Up to the yth term in relative_circuit, we have checked + # that absolute_circuit[y] == relative_circuit[y] + # This means absolute_circuit is commutable + # with command_i + # Set x = x+len(relative_circuit)-1 and continue through + # while loop as though the list was a commutable gate + x+=(len(relative_circuit)) + relative_commutable_circuits=[] + return x + return x + def _optimize(self, idx, lim=None): """ - Try to remove identity gates using the is_identity function, then merge or even cancel successive gates using the get_merged and + Try to remove identity gates using the is_identity function, + then merge or even cancel successive gates using the get_merged and get_inverse functions of the gate (see, e.g., BasicRotationGate). - It does so for all qubit command lists. """ # loop over all qubit indices i = 0 - new_gateloc = 0 limit = len(self._l[idx]) if lim is not None: limit = lim - new_gateloc = limit while i < limit - 1: - # can be dropped if the gate is equivalent to an identity gate - if self._l[idx][i].is_identity(): - # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] - gid = self._get_gate_indices(idx, i, qubitids) - for j in range(len(qubitids)): - new_list = (self._l[qubitids[j]][0:gid[j]] + - self._l[qubitids[j]][gid[j] +1:]) - self._l[qubitids[j]] = new_list + command_i = self._l[idx][i] + command_i_plus_1 = self._l[idx][i+1] + + # Delete command i if it is equivalent to identity + if command_i.is_identity(): + self._delete_command(idx, i) i = 0 limit -= 1 continue - - # can be dropped if two in a row are self-inverses - inv = self._l[idx][i].get_inverse() - - if inv == self._l[idx][i + 1]: - # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] - gid = self._get_gate_indices(idx, i, qubitids) - # check that there are no other gates between this and its - # inverse on any of the other qubits involved - erase = True - for j in range(len(qubitids)): - erase *= (inv == self._l[qubitids[j]][gid[j] + 1]) - - # drop these two gates if possible and goto next iteration - if erase: - for j in range(len(qubitids)): - new_list = (self._l[qubitids[j]][0:gid[j]] + - self._l[qubitids[j]][gid[j] + 2:]) - self._l[qubitids[j]] = new_list - i = 0 - limit -= 2 - continue - - # gates are not each other's inverses --> check if they're - # mergeable - try: - merged_command = self._l[idx][i].get_merged( - self._l[idx][i + 1]) - # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + + x = 0 + self._i_x_com = True # This boolean should be updated to represent whether + # the gates following i, up to and including x, are commutable + while (i+x+1 < limit): + if self._i_x_com: + # Gate i is commutable with each gate up to i+x, so + # check if i and i+x+1 can be cancelled or merged + inv = self._l[idx][i].get_inverse() + if inv == self._l[idx][i+x+1]: + # List of the indices of the qubits that are involved + # in command + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] - gid = self._get_gate_indices(idx, i, qubitids) + # List of the command indices corresponding to the position + # of this command on each qubit id + commandidcs = self._get_gate_indices(idx, i, qubitids) + erase = True + erase = self._get_erase_boolean(idx, qubitids, commandidcs, inv, self._apply_commutation) + if erase: + # Delete the inverse commands. Delete the later + # one first so the first index doesn't + # change before you delete it. + self._delete_command(idx, i+x+1) + self._delete_command(idx, i) + i = 0 + limit -= 2 + break + # Unsuccessful in cancelling inverses, try merging. + pass + try: + merged_command = self._l[idx][i].get_merged(self._l[idx][i+x+1]) + # determine index of this gate on all qubits + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + for qb in sublist] + commandidcs = self._get_gate_indices(idx, i, qubitids) + merge = True + merge = self._get_merge_boolean(idx, qubitids, commandidcs, + merged_command, self._apply_commutation) + if merge: + # Delete command i+x+1 first because i+x+1 + # will not affect index of i + self._delete_command(idx, i+x+1) + self._replace_command(idx, i, merged_command) + i = 0 + limit -= 1 + break + except NotMergeable: + # Unsuccessful in merging, see if gates are commutable + pass - merge = True - for j in range(len(qubitids)): - m = self._l[qubitids[j]][gid[j]].get_merged( - self._l[qubitids[j]][gid[j] + 1]) - merge *= (m == merged_command) - - if merge: - for j in range(len(qubitids)): - self._l[qubitids[j]][gid[j]] = merged_command - new_list = (self._l[qubitids[j]][0:gid[j] + 1] + - self._l[qubitids[j]][gid[j] + 2:]) - self._l[qubitids[j]] = new_list - i = 0 - limit -= 1 - continue - except NotMergeable: - pass # can't merge these two commands. + # If apply_commutation=False, then we want the optimizer to + # ignore commutation when optimizing + if not self._apply_commutation: + break + command_i = self._l[idx][i] + next_command = self._l[idx][i+x+1] + #----------------------------------------------------------# + # See if next_command is commutable with this_command. # # + #----------------------------------------------------------# + if(command_i.is_commutable(next_command) == 1): + x=x+1 + continue + #----------------------------------------------------------# + # See if next_command is part of a circuit which is # + # commutable with this_command. # + #----------------------------------------------------------# + new_x = 0 + if(command_i.is_commutable(next_command) == 2): + new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) + if(new_x>x): + x=new_x + self._i_x_com = True + continue + else: + self._i_x_com = False + break i += 1 # next iteration: look at next gate return limit @@ -255,4 +529,4 @@ def receive(self, command_list): assert self._l == dict() self.send([cmd]) else: - self._cache_cmd(cmd) + self._cache_cmd(cmd) \ No newline at end of file diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 121dbb471..e33994feb 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -15,16 +15,15 @@ """Tests for projectq.cengines._optimize.py.""" import pytest - import math from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import (CNOT, H, Rx, Ry, AllocateQubitGate, X, - FastForwardingGate, ClassicalInstructionGate) +from projectq.ops import (CNOT, H, Rx, Ry, Rz, Rxx, Ryy, Rzz, Measure, AllocateQubitGate, X, + FastForwardingGate, ClassicalInstructionGate, XGate) +from projectq.setups import restrictedgateset, trapped_ion_decomposer from projectq.cengines import _optimize - def test_local_optimizer_caching(): local_optimizer = _optimize.LocalOptimizer(m=4) backend = DummyEngine(save_commands=True) @@ -45,8 +44,6 @@ def test_local_optimizer_caching(): assert backend.received_commands[1].gate == H # Another gate on qb0 means it needs to send CNOT but clear pipeline of qb1 Rx(0.6) | qb0 - for cmd in backend.received_commands: - print(cmd) assert len(backend.received_commands) == 5 assert backend.received_commands[2].gate == AllocateQubitGate() assert backend.received_commands[3].gate == H @@ -55,7 +52,6 @@ def test_local_optimizer_caching(): assert backend.received_commands[4].control_qubits[0].id == qb0[0].id assert backend.received_commands[4].qubits[0][0].id == qb1[0].id - def test_local_optimizer_flush_gate(): local_optimizer = _optimize.LocalOptimizer(m=4) backend = DummyEngine(save_commands=True) @@ -70,7 +66,6 @@ def test_local_optimizer_flush_gate(): # Two allocate gates, two H gates and one flush gate assert len(backend.received_commands) == 5 - def test_local_optimizer_fast_forwarding_gate(): local_optimizer = _optimize.LocalOptimizer(m=4) backend = DummyEngine(save_commands=True) @@ -85,7 +80,6 @@ def test_local_optimizer_fast_forwarding_gate(): # As Deallocate gate is a FastForwardingGate, we should get gates of qb0 assert len(backend.received_commands) == 3 - def test_local_optimizer_cancel_inverse(): local_optimizer = _optimize.LocalOptimizer(m=4) backend = DummyEngine(save_commands=True) @@ -114,6 +108,32 @@ def test_local_optimizer_cancel_inverse(): assert received_commands[1].qubits[0][0].id == qb1[0].id assert received_commands[1].control_qubits[0].id == qb0[0].id +def test_local_optimizer_cancel_separated_inverse(): + # Tests the situation where an inverse of a command is found + # on the next qubit, but another qubit involved is separated + # from the inverse by only commutable gates. + local_optimizer = _optimize.LocalOptimizer(m=5) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + assert len(backend.received_commands) == 0 + Rxx(math.pi) | (qb0, qb1) + Rx(0.3) | qb1 + Rxx(-math.pi) | (qb0, qb1) + assert len(backend.received_commands) == 0 + Measure | qb0 + Measure | qb1 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 1 + assert received_commands[0].gate == Rx(0.3) + assert received_commands[0].qubits[0][0].id == qb1[0].id def test_local_optimizer_mergeable_gates(): local_optimizer = _optimize.LocalOptimizer(m=4) @@ -121,14 +141,73 @@ def test_local_optimizer_mergeable_gates(): eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it merges mergeable gates such as Rx qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() for _ in range(10): Rx(0.5) | qb0 - assert len(backend.received_commands) == 0 + for _ in range(10): + Ry(0.5) | qb0 + for _ in range(10): + Rz(0.5) | qb0 + # Test merge for Rxx, Ryy, Rzz with interchangeable qubits + Rxx(0.5) | (qb0, qb1) + Rxx(0.5) | (qb1, qb0) + Ryy(0.5) | (qb0, qb1) + Ryy(0.5) | (qb1, qb0) + Rzz(0.5) | (qb0, qb1) + Rzz(0.5) | (qb1, qb0) eng.flush() - # Expect allocate, one Rx gate, and flush gate - assert len(backend.received_commands) == 3 - assert backend.received_commands[1].gate == Rx(10 * 0.5) + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + # Expect one gate each of Rx, Ry, Rz, Rxx, Ryy, Rzz + assert len(received_commands) == 6 + assert received_commands[0].gate == Rx(10 * 0.5) + assert received_commands[1].gate == Ry(10 * 0.5) + assert received_commands[2].gate == Rz(10 * 0.5) + assert received_commands[3].gate == Rxx(1.0) + assert received_commands[4].gate == Ryy(1.0) + assert received_commands[5].gate == Rzz(1.0) +def test_local_optimizer_separated_mergeable_gates(): + # Tests the situation where a mergeable command is found + # on the next qubit, and another qubit involved in the command + # is separated from the mergeable gate by only commutable gates. + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + #assert len(backend.received_commands) == 0 + #Reminder: Rxx and Rx commute + Rxx(0.3) | (qb0, qb1) + Rx(math.pi) | qb1 + Rxx(0.8) | (qb0, qb1) + Rx(0.3) | qb1 + Rxx(1.2) | (qb0, qb1) + Ry(0.5) | qb1 + H | qb0 + assert len(backend.received_commands) == 0 + Measure | qb0 + Measure | qb1 + assert len(backend.received_commands) == 8 + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rxx(2.3) + assert received_commands[1].gate == H + assert received_commands[2].gate == Rx(math.pi+0.3) + assert received_commands[3].gate == Ry(0.5) + assert received_commands[0].qubits[0][0].id == qb0[0].id + assert received_commands[0].qubits[1][0].id == qb1[0].id + assert received_commands[1].qubits[0][0].id == qb0[0].id + assert received_commands[2].qubits[0][0].id == qb1[0].id + assert received_commands[3].qubits[0][0].id == qb1[0].id def test_local_optimizer_identity_gates(): local_optimizer = _optimize.LocalOptimizer(m=4) @@ -147,3 +226,488 @@ def test_local_optimizer_identity_gates(): # Expect allocate, one Rx gate, and flush gate assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(0.5) + +def test_local_optimizer_commutable_gates(): + # Test that inverse gates separated by two commutable gates + # cancel successfully and that mergeable gates separated by + # two commutable gates cancel successfully. + local_optimizer = _optimize.LocalOptimizer(m=5) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + qb3 = eng.allocate_qubit() + Rx(0.4) | qb0 + Rx(-math.pi) | qb1 + Rxx(0.3) | (qb1, qb0) + Rxx(0.5) | (qb0, qb1) + Rx(math.pi) | qb1 + Rx(0.5) | qb0 + Rxx(0.2) | (qb2, qb3) + Rx(0.3) | qb2 + Rx(0.2) | qb3 + Rxx(0.2) | (qb3, qb2) + assert len(backend.received_commands) == 0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate == Rx(0.9) + # If this test doesn't succeed check that the Rxx + # interchangeable qubits attribute is working. + assert received_commands[1].gate == Rxx(0.8) + assert received_commands[2].gate == Rxx(0.4) + assert received_commands[3].gate == Rx(0.3) + assert received_commands[4].gate == Rx(0.2) + assert received_commands[0].qubits[0][0].id == qb0[0].id + assert received_commands[1].qubits[0][0].id == qb0[0].id + assert received_commands[1].qubits[1][0].id == qb1[0].id + assert received_commands[2].qubits[0][0].id == qb2[0].id + assert received_commands[2].qubits[1][0].id == qb3[0].id + assert received_commands[3].qubits[0][0].id == qb2[0].id + assert received_commands[4].qubits[0][0].id == qb3[0].id + +def test_local_optimizer_commutable_circuit_Rz_example_1(): + # Rzs should merge + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rz(0.3) + assert len(received_commands) == 4 + +def test_local_optimizer_commutable_circuit_Rz_example_2(): + # Rzs shouldn't merge (Although in theory they should, this would require a new update.) + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb1 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb1 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[1].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_Rz_example_3(): + # Rzs should not merge because they are operating on different qubits + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb1 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[1].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_Rz_example_4(): + #Rzs shouldn't merge because they are operating on different qubits + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb1 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_Rz_example_5(): + # Rzs shouldn't merge because CNOT is the wrong orientation + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb0, qb1) + H | qb0 + Rz(0.2) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_Rz_example_6(): + # Rzs shouldn't merge (Although in theory they should, this would require a new update.) + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb1 + CNOT | (qb0, qb1) + H | qb1 + Rz(0.2) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_Rz_example_7(): + # Rzs shouldn't merge. Second H on wrong qubit. + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb1, qb0) + H | qb1 + Rz(0.2) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert received_commands[0].gate == Rz(0.1) + assert len(received_commands) == 5 + +def test_local_optimizer_commutable_circuit_CNOT_example_1(): + # This example should commute + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb2, qb0) + H | qb0 + CNOT | (qb0, qb1) + H | qb0 + CNOT | (qb2, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 3 + assert received_commands[0].gate == H + +def test_local_optimizer_commutable_circuit_CNOT_example_2(): + # This example should commute + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb1, qb0) + H | qb0 + CNOT | (qb0, qb2) + H | qb0 + CNOT | (qb1, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 3 + assert received_commands[0].gate == H + +def test_local_optimizer_commutable_circuit_CNOT_example_3(): + # This example should commute + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb0, qb1) + H | qb1 + CNOT | (qb1, qb2) + H | qb1 + CNOT | (qb0, qb1) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 3 + assert received_commands[0].gate == H + +def test_local_optimizer_commutable_circuit_CNOT_example_4(): + # This example shouldn't commute because the CNOT is the + # wrong orientation + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb1, qb0) + H | qb1 + CNOT | (qb1, qb2) + H | qb1 + CNOT | (qb1, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate.__class__ == XGate + +def test_local_optimizer_commutable_circuit_CNOT_example_5(): + # This example shouldn't commute because the CNOT is the + # wrong orientation + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb0, qb1) + H | qb1 + CNOT | (qb1, qb2) + H | qb1 + CNOT | (qb1, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate.__class__ == XGate + +def test_local_optimizer_commutable_circuit_CNOT_example_6(): + # This example shouldn't commute because the CNOT is the + # wrong orientation. Same as example_3 with middle CNOT reversed. + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb0, qb1) + H | qb1 + CNOT | (qb2, qb1) + H | qb1 + CNOT | (qb0, qb1) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate.__class__ == XGate + +def test_local_optimizer_commutable_circuit_CNOT_example_7(): + # This example shouldn't commute because the CNOT is the + # wrong orientation. Same as example_1 with middle CNOT reversed. + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb2, qb0) + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + CNOT | (qb2, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate.__class__ == XGate + +def test_local_optimizer_commutable_circuit_CNOT_example_8(): + # This example shouldn't commute because the CNOT is the + # wrong orientation. Same as example_2 with middle CNOT reversed. + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + CNOT | (qb1, qb0) + H | qb0 + CNOT | (qb2, qb0) + H | qb0 + CNOT | (qb1, qb0) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 5 + assert received_commands[0].gate.__class__ == XGate + +def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): + # This example is to check everything works as expected when + # the commutable circuit is on later commands of qubits + # The number of commmands should reduce from 10 to 7 + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb0 + CNOT | (qb0, qb1) + H | qb1 + CNOT | (qb1, qb2) + H | qb1 + CNOT | (qb0, qb1) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 7 + assert received_commands[6].gate == H + +def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_2(): + # This example is to check everything works as expected when + # the commutable circuit is on qubits 3,4,5 + local_optimizer = _optimize.LocalOptimizer(m=10) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + qb2 = eng.allocate_qubit() + qb3 = eng.allocate_qubit() + qb4 = eng.allocate_qubit() + Rz(0.1) | qb0 + H | qb0 + CNOT | (qb1, qb0) + H | qb0 + Rz(0.2) | qb0 + CNOT | (qb2, qb3) + H | qb3 + CNOT | (qb3, qb4) + H | qb3 + CNOT | (qb2, qb3) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 7 + assert received_commands[6].gate == H + +def test_local_optimizer_apply_commutation_false(): + # Test that the local_optimizer behaves as if commutation isn't an option + # if you set apply_commutation = False + local_optimizer = _optimize.LocalOptimizer(m=10, apply_commutation=False) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rz(0.1) | qb0 # Rzs next to eachother should merge + Rz(0.4) | qb0 + Rzz(0.3) | (qb0, qb1) # Rzs either side of Rzz should not merge + Rz(0.2) | qb0 + H | qb0 # Hs next to eachother should cancel + H | qb0 + Ry(0.1) | qb1 # Ry should not merge with the Rz on the other side of + H | qb0 # a commutable list + CNOT | (qb0, qb1) + H | qb0 + Ry(0.2) | qb1 + Rxx(0.2) | (qb0, qb1) + Rx(0.1) | qb1 # Rxxs either side of Rx shouldn't merge + Rxx(0.1) | (qb0, qb1) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 11 + assert received_commands[0].gate == Rz(0.5) + assert received_commands[2].gate == Rz(0.2) + assert received_commands[4].gate == Ry(0.1) + assert received_commands[7].gate == Ry(0.2) + assert received_commands[10].gate == Rxx(0.1) \ No newline at end of file diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index c7bdd31bc..bb8b2aa2e 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -101,6 +101,8 @@ def __init__(self): self.set_interchangeable_qubit_indices([[0,1],[2,3,4]]) """ self.interchangeable_qubit_indices = [] + self._commutable_gates = [] + self._commutable_circuit_list = [] def get_inverse(self): """ @@ -124,6 +126,10 @@ def get_merged(self, other): """ raise NotMergeable("BasicGate: No get_merged() implemented.") + def get_commutable_circuit_list(self, n=0): + """ Returns the list of commutable circuits associated with this gate. """ + return self._commutable_circuit_list + @staticmethod def make_tuple_of_qureg(qubits): """ @@ -240,6 +246,16 @@ def __hash__(self): def is_identity(self): return False + def is_commutable(self, other): + # If gate is commutable with other gate, return 1 + for gate in self._commutable_gates: + if (other.__class__ == gate): + return 1 + else: + # Default is to return False, including if + # other gate and gate are identical + return 0 + class MatrixGate(BasicGate): """ @@ -435,7 +451,7 @@ def is_identity(self): Return True if the gate is equivalent to an Identity gate """ return self.angle == 0. or self.angle == 4 * math.pi - + class BasicPhaseGate(BasicGate): """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index a58a24e4c..2225ef941 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -20,11 +20,13 @@ import pytest from projectq.types import Qubit, Qureg -from projectq.ops import Command, X +from projectq.ops import Command, X, NOT from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import _basics +from projectq.ops import _gates +from projectq.ops import _metagates @pytest.fixture @@ -336,3 +338,18 @@ def test_matrix_gate(): assert X == gate3 assert str(gate3) == "MatrixGate([[0, 1], [1, 0]])" assert hash(gate3) == hash("MatrixGate([[0, 1], [1, 0]])") + + +def test_is_commutable(): + # At the gate level is_commutable can return + # true or false (not maybe (2) this is at the command level) + gate1 = _basics.BasicRotationGate(math.pi) + gate2 = _basics.MatrixGate() + gate3 = _basics.BasicRotationGate(math.pi) + assert gate1.is_commutable(gate2) == False + assert gate1.is_commutable(gate3) == False + gate4 = _gates.Rz(math.pi) + gate5 = _gates.H + gate6 = _metagates.C(NOT) + assert gate4.is_commutable(gate5) == 0 + assert gate4.is_commutable(gate6) == 0 \ No newline at end of file diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 317356c19..5de61c3e2 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -39,11 +39,9 @@ """ from copy import deepcopy - import projectq from projectq.types import WeakQubitRef, Qureg - def apply_command(cmd): """ Apply a command. @@ -57,14 +55,14 @@ def apply_command(cmd): engine = cmd.engine engine.receive([cmd]) - class Command(object): """ - Class used as a container to store commands. If a gate is applied to - qubits, then the gate and qubits are saved in a command object. Qubits - are copied into WeakQubitRefs in order to allow early deallocation (would - be kept alive otherwise). WeakQubitRef qubits don't send deallocate gate - when destructed. + Class: + used as a container to store commands. If a gate is applied to + qubits, then the gate and qubits are saved in a command object. Qubits + are copied into WeakQubitRefs in order to allow early deallocation (would + be kept alive otherwise). WeakQubitRef qubits don't send deallocate gate + when destructed. Attributes: gate: The gate to execute @@ -115,6 +113,7 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): self.qubits = qubits # property self.control_qubits = controls # property self.engine = engine # property + self._commutable_circuit_list = self.gate.get_commutable_circuit_list(n=len(self.control_qubits)) @property def qubits(self): @@ -152,6 +151,24 @@ def is_identity(self): True if the gate is equivalent to an Identity gate, False otherwise """ return projectq.ops.is_identity(self.gate) + + def is_commutable(self, other): + """ + Evaluate if this command is commutable with another command. + Returns: + True if the other command concerns the same qubits and + the gates are commutable. + """ + if (overlap(self.all_qubits, other.all_qubits)==0): + return 0 + self._commutable_circuit_list = self.gate.get_commutable_circuit_list(len(self.control_qubits)) + # If other gate may be part of a list which is + # commutable with gate, return 2 + for circuit in self._commutable_circuit_list: + if (other.gate.__class__ == circuit[0]._gate.__class__): + return 2 + else: + return self.gate.is_commutable(other.gate) def get_merged(self, other): """ @@ -319,3 +336,25 @@ def to_string(self, symbols=False): qstring = qstring[:-2] + " )" cstring = "C" * len(ctrlqubits) return cstring + self.gate.to_string(symbols) + " | " + qstring + +def overlap(tuple1, tuple2): + """ + Takes two tuples of lists and turns them into, flattens + them and counts the number of common elements. Intended + to be used to see if two commands have qubits or control + qubits in common. + + i.e. command1.all_qubits = [[control_qubits], [qubits]] + command2.all_qubits = [[control_qubits], [qubits]] + overlap(command1, command2) = 4 + means command1 and command2 have 4 qubits or control + qubits in common. + + """ + flat_tuple1 = [item for sublist in tuple1 for item in sublist] + flat_tuple2 = [item for sublist in tuple2 for item in sublist] + n=0 + for element in flat_tuple1: + if element in flat_tuple2: + n+=1 + return n diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index b0b4d54c8..d7e3deb84 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -23,10 +23,9 @@ from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.meta import ComputeTag -from projectq.ops import BasicGate, Rx, NotMergeable +from projectq.ops import BasicGate, Rx, NotMergeable, Rxx, Ry, H, Rz, CNOT from projectq.types import Qubit, Qureg, WeakQubitRef - -from projectq.ops import _command +from projectq.ops import _command, _basics, RelativeCommand @pytest.fixture @@ -148,6 +147,41 @@ def test_command_is_identity(main_engine): assert not cmd2.gate.is_identity() +def test_overlap(): + tuple1 = ([1,2],[3]) + tuple2 = ([2],[3,0]) + tuple3 = ([0,0,0],) + assert _command.overlap(tuple1, tuple2) == 2 + assert _command.overlap(tuple1, tuple3) == 0 + + +def test_command_is_commutable(main_engine): + # Check is_commutable function returns 0, 1, 2 + # For False, True and Maybe + # 'Maybe' refers to the situation where you + # might have a commutable circuit + # CNOT's commutable circuit wont be recognised at this + # level because CNOT.__gate__ = ControlledGate + # whereas in the optimizer CNOT.__gate__ = XGate + qubit1 = Qureg([Qubit(main_engine, 0)]) + qubit2 = Qureg([Qubit(main_engine, 1)]) + cmd1 = _command.Command(main_engine, Rx(0.5), (qubit1,)) + cmd2 = _command.Command(main_engine, Rx(0.5), (qubit1,)) + cmd3 = _command.Command(main_engine, Rx(0.5), (qubit2,)) + cmd4 = _command.Command(main_engine, Rxx(0.5), (qubit1, qubit2)) + cmd5 = _command.Command(main_engine, Ry(0.2), (qubit1,)) + cmd6 = _command.Command(main_engine, Rz(0.2), (qubit1,)) + cmd7 = _command.Command(main_engine, H, (qubit1,)) + cmd8 = _command.Command(main_engine, CNOT, (qubit2,), qubit1) + assert not cmd1.is_commutable(cmd2) #Identical qubits, identical gate + assert _command.overlap(cmd1.all_qubits, cmd3.all_qubits) == 0 + assert not cmd1.is_commutable(cmd3) #Different qubits, same gate + assert cmd3.is_commutable(cmd4) #Qubits in common, different but commutable gates + assert not cmd4.is_commutable(cmd5) #Qubits in common, different, non-commutable gates + assert cmd6.is_commutable(cmd7) == 2 # Rz has a commutable circuit which starts with H + assert not cmd7.is_commutable(cmd8) # H does not have a commutable circuit which starts with CNOT + + def test_command_order_qubits(main_engine): qubit0 = Qureg([Qubit(main_engine, 0)]) qubit1 = Qureg([Qubit(main_engine, 1)]) @@ -242,6 +276,12 @@ def test_command_comparison(main_engine): cmd6.tags = ["TestTag"] cmd6.add_control_qubits(ctrl_qubit) assert cmd6 != cmd1 + # Test not equal because of location of ctrl qubits + ctrl_qubit2 = ctrl_qubit = Qureg([Qubit(main_engine, 2)]) + cmd7 = _command.Command(main_engine, Rx(0.5), (qubit,)) + cmd7.tags = ["TestTag"] + cmd7.add_control_qubits(ctrl_qubit2) + assert not cmd7 == cmd1 def test_command_str(): @@ -274,5 +314,4 @@ def test_command_to_string(): assert cmd2.to_string(symbols=False) == "Rx(1.570796326795) | Qureg[0]" else: assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" - assert cmd2.to_string(symbols=False) == "Rx(1.5707963268) | Qureg[0]" - + assert cmd2.to_string(symbols=False) == "Rx(1.5707963268) | Qureg[0]" \ No newline at end of file diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index be2240d00..7ed155003 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -56,8 +56,11 @@ ClassicalInstructionGate, FastForwardingGate, BasicMathGate) +from ._metagates import C from ._command import apply_command - +from ._command import Command +from ._relative_command import RelativeCommand +from projectq.types import BasicQubit class HGate(SelfInverseGate): """ Hadamard gate class """ @@ -74,6 +77,7 @@ def matrix(self): class XGate(SelfInverseGate): """ Pauli-X gate class """ + def __str__(self): return "X" @@ -81,10 +85,21 @@ def __str__(self): def matrix(self): return np.matrix([[0, 1], [1, 0]]) + def get_commutable_circuit_list(self, n=0): + """ Sets _commutable_circuit_list for C(NOT, n) where + n is the number of controls """ + if (n == 1): + # i.e. this is a CNOT gate (one control) + # We define the qubit with the NOT as qb0, the qubit with + # the control as qb1, then all next qbs are labelled 2,3 etc. + commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)), RelativeCommand(H,(0,))]] + return commutable_circuit_list + else: + return [] # don't change _commutable_circuit_list + #: Shortcut (instance of) :class:`projectq.ops.XGate` X = NOT = XGate() - class YGate(SelfInverseGate): """ Pauli-Y gate class """ def __str__(self): @@ -219,6 +234,11 @@ def matrix(self): class Rx(BasicRotationGate): """ RotationX gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self._commutable_gates = [Rxx,] + @property def matrix(self): return np.matrix([[math.cos(0.5 * self.angle), @@ -229,6 +249,11 @@ def matrix(self): class Ry(BasicRotationGate): """ RotationY gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self._commutable_gates = [Ryy,] + @property def matrix(self): return np.matrix([[math.cos(0.5 * self.angle), @@ -239,6 +264,12 @@ def matrix(self): class Rz(BasicRotationGate): """ RotationZ gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self._commutable_gates = [Rzz,] + self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + @property def matrix(self): return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0], @@ -247,6 +278,12 @@ def matrix(self): class Rxx(BasicRotationGate): """ RotationXX gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + self._commutable_gates = [Rx,] + @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, -1j*cmath.sin(.5 * self.angle)], @@ -257,6 +294,13 @@ def matrix(self): class Ryy(BasicRotationGate): """ RotationYY gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + self._commutable_gates = [Ry,] + + @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, 1j*cmath.sin(.5 * self.angle)], @@ -267,6 +311,12 @@ def matrix(self): class Rzz(BasicRotationGate): """ RotationZZ gate class """ + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + self._commutable_gates = [Rz,] + @property def matrix(self): return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0, 0, 0], @@ -287,7 +337,7 @@ class FlushGate(FastForwardingGate): Flush gate (denotes the end of the circuit). Note: - All compiler engines (cengines) which cache/buffer gates are obligated + All compiler engines (cengines) with cache/buffer gates are obligated to flush and send all gates to the next compiler engine (followed by the flush command). diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 88efa3a19..cfec3b3f9 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -25,6 +25,8 @@ FastForwardingGate, BasicGate, Measure) from projectq.ops import _gates +from projectq.ops import CNOT, H, C, NOT +from projectq.ops import RelativeCommand def test_h_gate(): @@ -37,12 +39,19 @@ def test_h_gate(): def test_x_gate(): - gate = _gates.XGate() - assert gate == gate.get_inverse() - assert str(gate) == "X" - assert np.array_equal(gate.matrix, np.matrix([[0, 1], [1, 0]])) + gate1 = _gates.XGate() + gate2 = CNOT + assert gate1 == gate1.get_inverse() + assert str(gate1) == "X" + assert np.array_equal(gate1.matrix, np.matrix([[0, 1], [1, 0]])) assert isinstance(_gates.X, _gates.XGate) assert isinstance(_gates.NOT, _gates.XGate) + assert gate1._commutable_circuit_list == [] + cmd1=RelativeCommand(H,(0,)) + cmd2=RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)) + cmd3=RelativeCommand(H,(0,)) + correct_commutable_circuit_list=[[cmd1,cmd2,cmd3],] + assert gate2.commutable_circuit_list == correct_commutable_circuit_list def test_y_gate(): @@ -122,74 +131,122 @@ def test_engangle_gate(): assert isinstance(_gates.Entangle, _gates.EntangleGate) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) -def test_rx(angle): - gate = _gates.Rx(angle) - expected_matrix = np.matrix([[math.cos(0.5 * angle), - -1j * math.sin(0.5 * angle)], - [-1j * math.sin(0.5 * angle), - math.cos(0.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, +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) -def test_ry(angle): - gate = _gates.Ry(angle) - expected_matrix = np.matrix([[math.cos(0.5 * angle), - -math.sin(0.5 * angle)], - [math.sin(0.5 * angle), - math.cos(0.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, +def test_rx(angle1, angle2): + gate1 = _gates.Rx(angle1) + gate2 = _gates.Rxx(angle2) + gate3 = _gates.Ry(angle1) + expected_matrix = np.matrix([[math.cos(0.5 * angle1), + -1j * math.sin(0.5 * angle1)], + [-1j * math.sin(0.5 * angle1), + math.cos(0.5 * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) -def test_rz(angle): - gate = _gates.Rz(angle) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle), 0], - [0, cmath.exp(.5 * 1j * 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, +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) -def test_rxx(angle): - gate = _gates.Rxx(angle) - expected_matrix = np.matrix([[cmath.cos(.5 * angle), 0, 0, -1j * cmath.sin(.5 * angle)], - [0, cmath.cos(.5 * angle), -1j * cmath.sin(.5 * angle), 0], - [0, -1j * cmath.sin(.5 * angle), cmath.cos(.5 * angle), 0], - [-1j * cmath.sin(.5 * angle), 0, 0, cmath.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, +def test_ry(angle1, angle2): + gate1 = _gates.Ry(angle1) + gate2 = _gates.Ryy(angle2) + gate3 = _gates.Rz(angle1) + expected_matrix = np.matrix([[math.cos(0.5 * angle1), + -math.sin(0.5 * angle1)], + [math.sin(0.5 * angle1), + math.cos(0.5 * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) -def test_ryy(angle): - gate = _gates.Ryy(angle) - expected_matrix = np.matrix([[cmath.cos(.5 * angle), 0, 0, 1j * cmath.sin(.5 * angle)], - [0, cmath.cos(.5 * angle), -1j * cmath.sin(.5 * angle), 0], - [0, -1j * cmath.sin(.5 * angle), cmath.cos(.5 * angle), 0], - [ 1j * cmath.sin(.5 * angle), 0, 0, cmath.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, +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) -def test_rzz(angle): - gate = _gates.Rzz(angle) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle), 0, 0, 0], - [0, cmath.exp( .5 * 1j * angle), 0, 0], - [0, 0, cmath.exp( .5 * 1j * angle), 0], - [0, 0, 0, cmath.exp(-.5 * 1j * angle)]]) - assert gate.matrix.shape == expected_matrix.shape - assert np.allclose(gate.matrix, expected_matrix) +def test_rz(angle1, angle2): + # At the gate level is_commutable can only return 0 or 1 + # to indicate whether the two gates are commutable + # At the command level is_commutable can return 2, to + # to indicate a possible commutable_circuit + gate1 = _gates.Rz(angle1) + gate2 = _gates.Rzz(angle2) + gate3 = _gates.Rx(angle1) + expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle1), 0], + [0, cmath.exp(.5 * 1j * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, + 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, + 4 * math.pi]) +def test_rxx(angle1, angle2): + gate1 = _gates.Rxx(angle1) + gate2 = _gates.Rx(angle2) + gate3 = _gates.Ry(angle1) + gate4 = _gates.Rxx(0.0) + expected_matrix = np.matrix([[cmath.cos(.5 * angle1), 0, 0, -1j * cmath.sin(.5 * angle1)], + [0, cmath.cos(.5 * angle1), -1j * cmath.sin(.5 * angle1), 0], + [0, -1j * cmath.sin(.5 * angle1), cmath.cos(.5 * angle1), 0], + [-1j * cmath.sin(.5 * angle1), 0, 0, cmath.cos(.5 * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + assert gate4.is_identity() + assert gate1.interchangeable_qubit_indices == [[0, 1]] + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, + 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, + 4 * math.pi]) +def test_ryy(angle1, angle2): + gate1 = _gates.Ryy(angle1) + gate2 = _gates.Ry(angle2) + gate3 = _gates.Rx(angle1) + gate4 = _gates.Ryy(0.0) + expected_matrix = np.matrix([[cmath.cos(.5 * angle1), 0, 0, 1j * cmath.sin(.5 * angle1)], + [0, cmath.cos(.5 * angle1), -1j * cmath.sin(.5 * angle1), 0], + [0, -1j * cmath.sin(.5 * angle1), cmath.cos(.5 * angle1), 0], + [ 1j * cmath.sin(.5 * angle1), 0, 0, cmath.cos(.5 * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + assert gate4.is_identity() + assert gate1.interchangeable_qubit_indices == [[0, 1]] + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, + 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, + 4 * math.pi]) +def test_rzz(angle1, angle2): + gate1 = _gates.Rzz(angle1) + gate2 = _gates.Rz(angle2) + gate3 = _gates.Ry(angle1) + gate4 = _gates.Rzz(0.0) + expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle1), 0, 0, 0], + [0, cmath.exp( .5 * 1j * angle1), 0, 0], + [0, 0, cmath.exp( .5 * 1j * angle1), 0], + [0, 0, 0, cmath.exp(-.5 * 1j * angle1)]]) + assert gate1.matrix.shape == expected_matrix.shape + assert np.allclose(gate1.matrix, expected_matrix) + assert gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) + assert gate4.is_identity() + assert gate1.interchangeable_qubit_indices == [[0, 1]] @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi]) diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index cca5e7412..df7e622f5 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -29,8 +29,6 @@ """ from ._basics import BasicGate, NotInvertible -from ._command import Command, apply_command - class ControlQubitError(Exception): """ @@ -38,7 +36,6 @@ class ControlQubitError(Exception): """ pass - class DaggeredGate(BasicGate): """ Wrapper class allowing to execute the inverse of a gate, even when it does @@ -111,7 +108,6 @@ def __eq__(self, other): def __hash__(self): return hash(str(self)) - def get_inverse(gate): """ Return the inverse of a gate. @@ -191,7 +187,7 @@ def __init__(self, gate, n=1): else: self._gate = gate self._n = n - + def __str__(self): """ Return string representation, i.e., CC...C(gate). """ return "C" * self._n + str(self._gate) @@ -246,6 +242,9 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) + @property + def commutable_circuit_list(self): + return self._gate.get_commutable_circuit_list(self._n) def C(gate, n=1): """ @@ -262,7 +261,6 @@ def C(gate, n=1): """ return ControlledGate(gate, n) - class Tensor(BasicGate): """ Wrapper class allowing to apply a (single-qubit) gate to every qubit in a diff --git a/projectq/ops/_relative_command.py b/projectq/ops/_relative_command.py new file mode 100644 index 000000000..1429d3877 --- /dev/null +++ b/projectq/ops/_relative_command.py @@ -0,0 +1,69 @@ +from projectq.ops._metagates import ControlledGate + +class RelativeCommand(object): + """ + Class: + Used to represent commands when there is no engine. + i.e. in the definition of a relative commutable_circuit + within a gate class. + + Attributes: + gate: The gate class. + _gate: If RelativeCommand.gate = ControlledGate, _gate will + return the gate class on the target qubit. + e.g. if relative_command.gate = CNOT + (class = projectq.ops._metagates.ControlledGate) + relative_command._gate = NOT + (class = projectq.ops._gates.XGate) + relative_qubit_idcs: Tuple of integers, representing the + relative qubit idcs in a commutable_circuit. + relative_ctrl_idcs: Tuple of integers, representing the + relative control qubit idcs in a commutable_circuit. + """ + def __init__(self, gate, relative_qubit_idcs, relative_ctrl_idcs=()): + self.gate = gate + self.relative_qubit_idcs = relative_qubit_idcs + self.relative_ctrl_idcs = relative_ctrl_idcs + self._gate = gate + if (self.gate.__class__ == ControlledGate): + self._gate = self.gate._gate + + def __str__(self): + return self.to_string() + + def __eq__(self, other): + return self.equals(other) + + def to_string(self, symbols=False): + """ + Get string representation of this Command object. + """ + print('i should not exist') + qubits = self.relative_qubit_idcs + ctrlqubits = self.relative_ctrl_idcs + if len(ctrlqubits) > 0: + qubits = (self.relative_ctrl_idcs, ) + qubits + qstring = "" + if len(qubits) == 1: + qstring = str(qubits) + else: + qstring = "( " + for qreg in range(len(qubits)): + qstring += str(qubits[qreg]) + qstring += ", " + qstring = qstring[:-2] + " )" + return self.gate.to_string(symbols) + " | " + qstring + + def equals(self, other): + if other is None: + return False + if (self.gate.__class__ != other.gate.__class__): + return False + if (self.relative_qubit_idcs != other.relative_qubit_idcs): + return False + if (self.relative_ctrl_idcs != other.relative_ctrl_idcs): + return False + if (self._gate.__class__ != self._gate.__class__): + return False + else: + return True diff --git a/projectq/ops/_relative_command_test.py b/projectq/ops/_relative_command_test.py new file mode 100644 index 000000000..7c7c5efbf --- /dev/null +++ b/projectq/ops/_relative_command_test.py @@ -0,0 +1,18 @@ +from projectq.ops import H, CNOT +from projectq.ops import _basics, RelativeCommand + +def test_relative_command_equals(): + cmd1 = RelativeCommand(H, 0) + cmd2 = RelativeCommand(H, 0) + cmd3 = RelativeCommand(H, 1) + cmd4 = RelativeCommand(CNOT, 0, 1) + cmd5 = RelativeCommand(CNOT, 0, 1) + cmd6 = RelativeCommand(CNOT, 1, 0) + cmd7 = RelativeCommand(CNOT, 0, 2) + cmd8 = RelativeCommand(CNOT, 2, 1) + assert cmd1 == cmd2 + assert cmd1 != cmd3 + assert cmd4 == cmd5 + assert cmd4 != cmd6 + assert cmd4 != cmd7 + assert cmd4 != cmd8 diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index fe0c00ba2..8a89f6ce7 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -65,7 +65,8 @@ def default_chooser(cmd, decomposition_list): def get_engine_list(one_qubit_gates="any", two_qubit_gates=(CNOT, ), other_gates=(), - compiler_chooser=default_chooser): + compiler_chooser=default_chooser, + apply_commutation=True): """ Returns an engine list to compile to a restricted gate set. @@ -86,7 +87,9 @@ def get_engine_list(one_qubit_gates="any", Example: get_engine_list(one_qubit_gates=(Rz, Ry, Rx, H), two_qubit_gates=(CNOT,), - other_gates=(TimeEvolution,)) + other_gates=(TimeEvolution,) + compiler_chooser=chooser_Ry_reducer, + apply_commutation=True) Args: one_qubit_gates: "any" allows any one qubit gate, otherwise provide a @@ -106,7 +109,9 @@ def get_engine_list(one_qubit_gates="any", which are equal to it. If the gate is a class, it allows all instances of this class. compiler_chooser:function selecting the decomposition to use in the - Autoreplacer engine + Autoreplacer engine. + apply_commutation: tells the LocalOptimizer engine whether to consider + commutation rules during optimization. Raises: TypeError: If input is for the gates is not "any" or a tuple. Also if element within tuple is not a class or instance of BasicGate @@ -206,13 +211,13 @@ def low_level_gates(eng, cmd): AutoReplacer(rule_set, compiler_chooser), TagRemover(), InstructionFilter(high_level_gates), - LocalOptimizer(5), + LocalOptimizer(5, apply_commutation=apply_commutation), AutoReplacer(rule_set, compiler_chooser), TagRemover(), InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), + LocalOptimizer(5, apply_commutation=apply_commutation), AutoReplacer(rule_set, compiler_chooser), TagRemover(), InstructionFilter(low_level_gates), - LocalOptimizer(5), + LocalOptimizer(5, apply_commutation=apply_commutation), ] From 680784c98fd53cfeeea37a5a30a02c3e085b0ac4 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Mon, 22 Feb 2021 18:38:39 +0000 Subject: [PATCH 02/16] Update to comments and docstrings --- projectq/cengines/_optimize.py | 53 +++++++++++++++++--- projectq/cengines/_optimize_test.py | 77 ++++++++++++++++------------- projectq/ops/_basics.py | 18 ++++++- projectq/ops/_basics_test.py | 5 +- projectq/ops/_command.py | 11 +++-- projectq/ops/_command_test.py | 18 ++++--- projectq/ops/_gates.py | 5 +- projectq/ops/_relative_command.py | 68 ++++++++++++++++++++++--- 8 files changed, 187 insertions(+), 68 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 9029eb19c..feea58dba 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -115,6 +115,10 @@ def _delete_command(self, idx, command_idx): """ Deletes the command at self._l[idx][command_idx] accounting for all qubits in the optimizer dictionary. + + Args: + idx (int): qubit index + command_idx (int): command position in qubit idx's command list """ # List of the indices of the qubits that are involved # in command @@ -136,6 +140,12 @@ def _replace_command(self, idx, command_idx, new_command): """ Replaces the command at self._l[idx][command_idx] accounting for all qubits in the optimizer dictionary. + + Args: + idx (int): qubit index + command_idx (int): command position in qubit idx's command list + new_command (Command): The command to replace the command + at self._l[idx][command_idx] """ # List of the indices of the qubits that are involved # in command @@ -159,6 +169,15 @@ def _get_erase_boolean(self, idx, qubitids, commandidcs, inverse_command, apply_ Determines whether inverse commands should be cancelled with one another. i.e. the commands between the pair are all commutable for each qubit involved in the command. + + Args: + idx (int): qubit index + qubitids (list of int): the qubit ids involved in the command we're examining. + commandidcs (list of int): command position in qubit idx's command list. + inverse_command (Command): the command to be cancelled with. + apply_commutation (bool): True/False depending on whether optimizer + is considering commutation rules. + """ erase = True # We dont want to examine qubit idx because the optimizer @@ -206,10 +225,18 @@ def _get_merge_boolean(self, idx, qubitids, commandidcs, merged_command, apply_c """ To determine whether mergeable commands should be merged with one another. i.e. the commands between them are all - commutable, for each qubit involved in the command. It does + commutable for each qubit involved in the command. It does not check for the situation where commands are separated by a commutable list. However other parts of the optimizer should find this situation. + + Args: + idx (int): qubit index + qubitids (list of int): the qubit ids involved in the command we're examining. + commandidcs (list of int): command position in qubit idx's command list. + merged_command (Command): the merged command we want to produce. + apply_commutation (bool): True/False depending on whether optimizer + is considering commutation rules. """ merge = True # We dont want to examine qubit idx because the optimizer has already @@ -248,13 +275,23 @@ def _get_merge_boolean(self, idx, qubitids, commandidcs, merged_command, apply_c return merge def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): - """ command_i = current command - next_command = the next command - idx = index of the current qubit in the optimizer - i = index of the current command in the optimizer - x = number of commutable gates infront of i we have found - (if there is a commutable circuit, we pretend we have found - x commutable gates where x is the length of the commutable circuit """ + """ + Checks if there is a commutable circuit separating two commands. + + Args: + command_i (Command) = current command + next_command (Command) = the next command + idx (int) = index of the current qubit in the optimizer + i (int) = index of the current command in the optimizer + x (int) = number of commutable gates infront of i we have found + (if there is a commutable circuit, we pretend we have found + x commutable gates where x is the length of the commutable circuit.) + + Returns: + x (int): If there is a commutable circuit the function returns the length x. + Otherwise, returns 0. + + """ # commutable_circuit_list is a temp variable just used to create relative_commutable_circuits commutable_circuit_list = command_i.gate.get_commutable_circuit_list(n=len(command_i._control_qubits), ) relative_commutable_circuits = [] diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index e33994feb..7208ffaee 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -109,9 +109,10 @@ def test_local_optimizer_cancel_inverse(): assert received_commands[1].control_qubits[0].id == qb0[0].id def test_local_optimizer_cancel_separated_inverse(): - # Tests the situation where an inverse of a command is found - # on the next qubit, but another qubit involved is separated - # from the inverse by only commutable gates. + """ Tests the situation where the next command on + this qubit is an inverse command, but another qubit + involved is separated from the inverse by only commutable + gates. The two commands should cancel. """ local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -172,9 +173,11 @@ def test_local_optimizer_mergeable_gates(): assert received_commands[5].gate == Rzz(1.0) def test_local_optimizer_separated_mergeable_gates(): - # Tests the situation where a mergeable command is found - # on the next qubit, and another qubit involved in the command - # is separated from the mergeable gate by only commutable gates. + """Tests the situation where the next command on this qubit + is a mergeable command, but another qubit involved is separated + from the mergeable command by only commutable gates. + The commands should merge. + """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -228,9 +231,10 @@ def test_local_optimizer_identity_gates(): assert backend.received_commands[1].gate == Rx(0.5) def test_local_optimizer_commutable_gates(): - # Test that inverse gates separated by two commutable gates - # cancel successfully and that mergeable gates separated by - # two commutable gates cancel successfully. + """ Test that inverse gates separated by two commutable gates + cancel successfully and that mergeable gates separated by + two commutable gates cancel successfully. + """ local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -273,6 +277,7 @@ def test_local_optimizer_commutable_gates(): assert received_commands[4].qubits[0][0].id == qb3[0].id def test_local_optimizer_commutable_circuit_Rz_example_1(): + """ Example circuit where the Rzs should merge. """ # Rzs should merge local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) @@ -295,7 +300,8 @@ def test_local_optimizer_commutable_circuit_Rz_example_1(): assert len(received_commands) == 4 def test_local_optimizer_commutable_circuit_Rz_example_2(): - # Rzs shouldn't merge (Although in theory they should, this would require a new update.) + """ Rzs shouldn't merge (Although in theory they should, + this would require a new update.) """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -317,7 +323,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_2(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_3(): - # Rzs should not merge because they are operating on different qubits + """ Rzs should not merge because they are operating on different qubits. """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -339,7 +345,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_3(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_4(): - #Rzs shouldn't merge because they are operating on different qubits + """Rzs shouldn't merge because they are operating on different qubits.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -361,7 +367,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_4(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_5(): - # Rzs shouldn't merge because CNOT is the wrong orientation + """Rzs shouldn't merge because CNOT is the wrong orientation.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -383,7 +389,8 @@ def test_local_optimizer_commutable_circuit_Rz_example_5(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_6(): - # Rzs shouldn't merge (Although in theory they should, this would require a new update.) + """Rzs shouldn't merge (Although in theory they should, + this would require a new update.)""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -405,7 +412,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_6(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_7(): - # Rzs shouldn't merge. Second H on wrong qubit. + """Rzs shouldn't merge. Second H on wrong qubit.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -427,7 +434,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_7(): assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_CNOT_example_1(): - # This example should commute + """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -450,7 +457,7 @@ def test_local_optimizer_commutable_circuit_CNOT_example_1(): assert received_commands[0].gate == H def test_local_optimizer_commutable_circuit_CNOT_example_2(): - # This example should commute + """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -473,7 +480,7 @@ def test_local_optimizer_commutable_circuit_CNOT_example_2(): assert received_commands[0].gate == H def test_local_optimizer_commutable_circuit_CNOT_example_3(): - # This example should commute + """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -496,8 +503,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_3(): assert received_commands[0].gate == H def test_local_optimizer_commutable_circuit_CNOT_example_4(): - # This example shouldn't commute because the CNOT is the - # wrong orientation + """This example shouldn't commute because the CNOT is the + wrong orientation.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -520,8 +527,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_4(): assert received_commands[0].gate.__class__ == XGate def test_local_optimizer_commutable_circuit_CNOT_example_5(): - # This example shouldn't commute because the CNOT is the - # wrong orientation + """This example shouldn't commute because the CNOT is the + wrong orientation.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -544,8 +551,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_5(): assert received_commands[0].gate.__class__ == XGate def test_local_optimizer_commutable_circuit_CNOT_example_6(): - # This example shouldn't commute because the CNOT is the - # wrong orientation. Same as example_3 with middle CNOT reversed. + """This example shouldn't commute because the CNOT is the + wrong orientation. Same as example_3 with middle CNOT reversed.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -568,8 +575,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_6(): assert received_commands[0].gate.__class__ == XGate def test_local_optimizer_commutable_circuit_CNOT_example_7(): - # This example shouldn't commute because the CNOT is the - # wrong orientation. Same as example_1 with middle CNOT reversed. + """This example shouldn't commute because the CNOT is the + wrong orientation. Same as example_1 with middle CNOT reversed.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -592,8 +599,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_7(): assert received_commands[0].gate.__class__ == XGate def test_local_optimizer_commutable_circuit_CNOT_example_8(): - # This example shouldn't commute because the CNOT is the - # wrong orientation. Same as example_2 with middle CNOT reversed. + """This example shouldn't commute because the CNOT is the + wrong orientation. Same as example_2 with middle CNOT reversed.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -616,9 +623,9 @@ def test_local_optimizer_commutable_circuit_CNOT_example_8(): assert received_commands[0].gate.__class__ == XGate def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): - # This example is to check everything works as expected when - # the commutable circuit is on later commands of qubits - # The number of commmands should reduce from 10 to 7 + """This example is to check everything works as expected when + the commutable circuit is on later commands in the optimizer + dictionary. The number of commmands should reduce from 10 to 7. """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -646,8 +653,8 @@ def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): assert received_commands[6].gate == H def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_2(): - # This example is to check everything works as expected when - # the commutable circuit is on qubits 3,4,5 + """ This example is to check everything works as expected when + the commutable circuit is on qubits 3, 4, 5. """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -677,8 +684,8 @@ def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_2(): assert received_commands[6].gate == H def test_local_optimizer_apply_commutation_false(): - # Test that the local_optimizer behaves as if commutation isn't an option - # if you set apply_commutation = False + """Test that the local_optimizer behaves as if commutation isn't an option + if you set apply_commutation = False. """ local_optimizer = _optimize.LocalOptimizer(m=10, apply_commutation=False) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index bb8b2aa2e..d7d40ae94 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -127,7 +127,12 @@ def get_merged(self, other): raise NotMergeable("BasicGate: No get_merged() implemented.") def get_commutable_circuit_list(self, n=0): - """ Returns the list of commutable circuits associated with this gate. """ + """ Returns the list of commutable circuits associated with this gate. + + Args: + n (int): The CNOT gate needs to be able to pass in parameter n in + this method. + """ return self._commutable_circuit_list @staticmethod @@ -247,7 +252,16 @@ def is_identity(self): return False def is_commutable(self, other): - # If gate is commutable with other gate, return 1 + """Determine whether this gate is commutable with + another gate. + + Args: + other (Gate): The other gate. + + Returns: + 1, if the gates are commutable. + 0, if the gates are not commutable. + """ for gate in self._commutable_gates: if (other.__class__ == gate): return 1 diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 2225ef941..5aa67659f 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -341,8 +341,9 @@ def test_matrix_gate(): def test_is_commutable(): - # At the gate level is_commutable can return - # true or false (not maybe (2) this is at the command level) + """ At the gate level is_commutable can return + true or false. Test that is_commutable is working + as expected. """ gate1 = _basics.BasicRotationGate(math.pi) gate2 = _basics.MatrixGate() gate3 = _basics.BasicRotationGate(math.pi) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 5de61c3e2..385191139 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -155,6 +155,10 @@ def is_identity(self): def is_commutable(self, other): """ Evaluate if this command is commutable with another command. + + Args: + other (Command): The other command. + Returns: True if the other command concerns the same qubits and the gates are commutable. @@ -339,10 +343,9 @@ def to_string(self, symbols=False): def overlap(tuple1, tuple2): """ - Takes two tuples of lists and turns them into, flattens - them and counts the number of common elements. Intended - to be used to see if two commands have qubits or control - qubits in common. + Takes two tuples of lists, flattens them and counts the number + of common elements. Used to check if two commands have qubits + or control qubits in common. i.e. command1.all_qubits = [[control_qubits], [qubits]] command2.all_qubits = [[control_qubits], [qubits]] diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index d7e3deb84..c165a2bfd 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -25,7 +25,7 @@ from projectq.meta import ComputeTag from projectq.ops import BasicGate, Rx, NotMergeable, Rxx, Ry, H, Rz, CNOT from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import _command, _basics, RelativeCommand +from projectq.ops import _command, _basics @pytest.fixture @@ -148,6 +148,8 @@ def test_command_is_identity(main_engine): def test_overlap(): + """ Test the overlap function is working as + expected.""" tuple1 = ([1,2],[3]) tuple2 = ([2],[3,0]) tuple3 = ([0,0,0],) @@ -156,13 +158,13 @@ def test_overlap(): def test_command_is_commutable(main_engine): - # Check is_commutable function returns 0, 1, 2 - # For False, True and Maybe - # 'Maybe' refers to the situation where you - # might have a commutable circuit - # CNOT's commutable circuit wont be recognised at this - # level because CNOT.__gate__ = ControlledGate - # whereas in the optimizer CNOT.__gate__ = XGate + """Check is_commutable function returns 0, 1, 2 + For False, True and Maybe + 'Maybe' refers to the situation where you + might have a commutable circuit + CNOT's commutable circuit wont be recognised at this + level because CNOT.__gate__ = ControlledGate + whereas in the optimizer CNOT.__gate__ = XGate. """ qubit1 = Qureg([Qubit(main_engine, 0)]) qubit2 = Qureg([Qubit(main_engine, 1)]) cmd1 = _command.Command(main_engine, Rx(0.5), (qubit1,)) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 7ed155003..488ad5659 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -87,7 +87,10 @@ def matrix(self): def get_commutable_circuit_list(self, n=0): """ Sets _commutable_circuit_list for C(NOT, n) where - n is the number of controls """ + n is the number of controls + + Args: + n (int): The number of controls on this gate. """ if (n == 1): # i.e. this is a CNOT gate (one control) # We define the qubit with the NOT as qb0, the qubit with diff --git a/projectq/ops/_relative_command.py b/projectq/ops/_relative_command.py index 1429d3877..1dbab37e0 100644 --- a/projectq/ops/_relative_command.py +++ b/projectq/ops/_relative_command.py @@ -1,7 +1,59 @@ +# Copyright 2017 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. +""" +This file defines the RelativeCommand class. + +RelativeCommand should be used to represent a Command +when there is no main_engine, for example within Gate +definitions. Using RelativeCommand we can define +commutable_circuits for a particular gate. + +Example: + +.. code-block:: python + + class Rz(BasicRotationGate): + + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self._commutable_gates = [Rzz,] + self._commutable_circuit_list = [[RelativeCommand(H,(0,)), + RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), + RelativeCommand(H,(0,))],] + +The _commutable_circuit_list has been defined using RelativeCommand +objects. The is_commutable function defined in the Command class looks +for _commutable_circuits in the definition of its' gate. Then we can +check if there might be a commutable circuit coming after a Command. +This is used in the _check_for_commutable_circuit function and in the +LocalOptimizer in _optimize.py + + cmd1 = _command.Command(main_engine, Rz(0.2), (qubit1,)) + cmd2 = _command.Command(main_engine, H, (qubit1,)) + if (cmd1.is_commutable(cmd2) == 2): + # Check for a commutable circuit + +Rz has a commutable circuit which starts with H. If is_commutable returns '2' +it indicates that the next command may be the start of a commutable circuit. + +""" + from projectq.ops._metagates import ControlledGate class RelativeCommand(object): - """ + """ Alternative representation of a Command. + Class: Used to represent commands when there is no engine. i.e. in the definition of a relative commutable_circuit @@ -10,15 +62,15 @@ class RelativeCommand(object): Attributes: gate: The gate class. _gate: If RelativeCommand.gate = ControlledGate, _gate will - return the gate class on the target qubit. - e.g. if relative_command.gate = CNOT - (class = projectq.ops._metagates.ControlledGate) - relative_command._gate = NOT - (class = projectq.ops._gates.XGate) + return the gate class on the target qubit. + e.g. if relative_command.gate = CNOT + (class = projectq.ops._metagates.ControlledGate) + then relative_command._gate = NOT + (class = projectq.ops._gates.XGate) relative_qubit_idcs: Tuple of integers, representing the - relative qubit idcs in a commutable_circuit. + relative qubit idcs in a commutable_circuit. relative_ctrl_idcs: Tuple of integers, representing the - relative control qubit idcs in a commutable_circuit. + relative control qubit idcs in a commutable_circuit. """ def __init__(self, gate, relative_qubit_idcs, relative_ctrl_idcs=()): self.gate = gate From 9987f6a4484cfc7a507cab73db404987d724d6ba Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Sat, 13 Mar 2021 17:10:05 +0000 Subject: [PATCH 03/16] Minor and syntax changes --- projectq/cengines/_optimize.py | 192 ++++++++++++++---------------- projectq/ops/_basics.py | 4 +- projectq/ops/_command.py | 6 +- projectq/ops/_relative_command.py | 17 +-- 4 files changed, 101 insertions(+), 118 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index feea58dba..13ff4fc7b 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -38,12 +38,13 @@ def __init__(self, m=5, apply_commutation=True): Args: m (int): Number of gates to cache per qubit, before sending on the first gate. + apply_commutation (Boolean): Indicates whether to consider commutation + rules during optimization. """ BasicEngine.__init__(self) self._l = dict() # dict of lists containing operations for each qubit self._m = m # wait for m gates before sending on self._apply_commutation = apply_commutation - self._i_x_com = False def _send_qubit_pipeline(self, idx, n): """ @@ -131,7 +132,7 @@ def _delete_command(self, idx, command_idx): try: new_list = (self._l[qubitids[j]][0:commandidcs[j]] + self._l[qubitids[j]][commandidcs[j]+1:]) - except: + except IndexError: # If there are no more commands after that being deleted. new_list = (self._l[qubitids[j]][0:commandidcs[j]]) self._l[qubitids[j]] = new_list @@ -147,6 +148,9 @@ def _replace_command(self, idx, command_idx, new_command): new_command (Command): The command to replace the command at self._l[idx][command_idx] """ + # Check that the new command concerns the same qubits as the original + # command before starting the replacement process + assert new_command.all_qubits == self._l[idx][command_idx].all_qubits # List of the indices of the qubits that are involved # in command qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits @@ -159,12 +163,12 @@ def _replace_command(self, idx, command_idx, new_command): new_list = (self._l[qubitids[j]][0:commandidcs[j]] + [new_command] + self._l[qubitids[j]][commandidcs[j]+1:]) - except: + except IndexError: # If there are no more commands after that being replaced. new_list = (self._l[qubitids[j]][0:commandidcs[j]] + [new_command]) self._l[qubitids[j]] = new_list - def _get_erase_boolean(self, idx, qubitids, commandidcs, inverse_command, apply_commutation): + def _can_cancel_by_commutation(self, idx, qubitids, commandidcs, inverse_command, apply_commutation): """ Determines whether inverse commands should be cancelled with one another. i.e. the commands between the pair are all @@ -221,7 +225,7 @@ def _get_erase_boolean(self, idx, qubitids, commandidcs, inverse_command, apply_ break return erase - def _get_merge_boolean(self, idx, qubitids, commandidcs, merged_command, apply_commutation): + def _can_merge_by_commutation(self, idx, qubitids, commandidcs, merged_command, apply_commutation): """ To determine whether mergeable commands should be merged with one another. i.e. the commands between them are all @@ -298,7 +302,7 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): # Keep a list of circuits that start with # next_command. for relative_circuit in commutable_circuit_list: - if (relative_circuit[0].gate.__class__ == next_command.gate.__class__): + if type(relative_circuit[0].gate) is type(next_command.gate): relative_commutable_circuits.append(relative_circuit) # Create dictionaries { absolute_qubit_idx : relative_qubit_idx } # For the purposes of fast lookup, also { relative_qubit_idx : absolute_qubit_idx } @@ -306,7 +310,7 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): rel_to_abs = { 0 : idx } # If the current command is a CNOT, we set the target qubit idx # to 0 - if command_i.gate.__class__==XGate: + if isinstance(command_i.gate, XGate): if len(command_i._control_qubits)==1: # At this point we know we have a CNOT # we reset the dictionaries so that the @@ -319,7 +323,7 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): absolute_circuit = self._l[idx][i+x+1:] # If no (more) relative commutable circuits to check against, # break out of this while loop and move on to next command_i. - while(len(relative_commutable_circuits)>0): + while relative_commutable_circuits: # If all the viable relative_circuits have been deleted # you want to just move on relative_circuit = relative_commutable_circuits[0] @@ -330,14 +334,14 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): # The absolute circuit is too short to match the relative_circuit # i.e. if the absolute circuit is of len=3, you can't have absolute_circuit[3] # only absolute_circuit[0] - absolute_circuit[2] - if (len(relative_commutable_circuits)!=0): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break # Check if relative_circuit command # matches the absolute_circuit command next_command = absolute_circuit[y] - if not (relative_circuit[y]._gate.__class__==next_command.gate.__class__): - if (len(relative_commutable_circuits)!=0): + if not type(relative_circuit[y]._gate) is type(next_command.gate): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break @@ -350,22 +354,18 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): r=relative_circuit[y].relative_qubit_idcs[0] if a in abs_to_rel.keys(): # If a in abs_to_rel, r will be in rel_to_abs - if (abs_to_rel[a] != r): - # Put it in a try block because pop will fail - # if relative_commutable_circuits already empty. - if (len(relative_commutable_circuits)!=0): + if (abs_to_rel[a] != r): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break if r in rel_to_abs.keys(): if (rel_to_abs[r] != a): - # Put it in a try block because pop will fail - # if relative_commutable_circuits already empty. - if (len(relative_commutable_circuits)!=0): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break abs_to_rel[a] = r rel_to_abs[r] = a - if(len(relative_commutable_circuits)==0): + if not relative_commutable_circuits: break # HERE: we know the qubit idcs don't contradict our dictionaries. for ctrl_qubit in next_command.control_qubits: @@ -374,22 +374,18 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): r=relative_circuit[y].relative_ctrl_idcs[0] if a in abs_to_rel.keys(): # If a in abs_to_rel, r will be in rel_to_abs - if (abs_to_rel[a] != r): - # Put it in a try block because pop will fail - # if relative_commutable_circuits already empty. - if (len(relative_commutable_circuits)!=0): + if (abs_to_rel[a] != r): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break if r in rel_to_abs.keys(): - if (rel_to_abs[r] != a): - # Put it in a try block because pop will fail - # if relative_commutable_circuits already empty. - if (len(relative_commutable_circuits)!=0): + if (rel_to_abs[r] != a): + if relative_commutable_circuits: relative_commutable_circuits.pop(0) break abs_to_rel[a] = r rel_to_abs[r] = a - if(len(relative_commutable_circuits)==0): + if not relative_commutable_circuits: break # HERE: we know all relative/absolute qubits/ctrl qubits do not # contradict dictionaries and are assigned. @@ -421,7 +417,6 @@ def _optimize(self, idx, lim=None): while i < limit - 1: command_i = self._l[idx][i] - command_i_plus_1 = self._l[idx][i+1] # Delete command i if it is equivalent to identity if command_i.is_identity(): @@ -431,82 +426,75 @@ def _optimize(self, idx, lim=None): continue x = 0 - self._i_x_com = True # This boolean should be updated to represent whether - # the gates following i, up to and including x, are commutable while (i+x+1 < limit): - if self._i_x_com: - # Gate i is commutable with each gate up to i+x, so - # check if i and i+x+1 can be cancelled or merged - inv = self._l[idx][i].get_inverse() - if inv == self._l[idx][i+x+1]: - # List of the indices of the qubits that are involved - # in command - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] - # List of the command indices corresponding to the position - # of this command on each qubit id - commandidcs = self._get_gate_indices(idx, i, qubitids) - erase = True - erase = self._get_erase_boolean(idx, qubitids, commandidcs, inv, self._apply_commutation) - if erase: - # Delete the inverse commands. Delete the later - # one first so the first index doesn't - # change before you delete it. - self._delete_command(idx, i+x+1) - self._delete_command(idx, i) - i = 0 - limit -= 2 - break - # Unsuccessful in cancelling inverses, try merging. - pass - try: - merged_command = self._l[idx][i].get_merged(self._l[idx][i+x+1]) - # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] - commandidcs = self._get_gate_indices(idx, i, qubitids) - merge = True - merge = self._get_merge_boolean(idx, qubitids, commandidcs, - merged_command, self._apply_commutation) - if merge: - # Delete command i+x+1 first because i+x+1 - # will not affect index of i - self._delete_command(idx, i+x+1) - self._replace_command(idx, i, merged_command) - i = 0 - limit -= 1 - break - except NotMergeable: - # Unsuccessful in merging, see if gates are commutable - pass - - # If apply_commutation=False, then we want the optimizer to - # ignore commutation when optimizing - if not self._apply_commutation: + # At this point: + # Gate i is commutable with each gate up to i+x, so + # check if i and i+x+1 can be cancelled or merged + inv = self._l[idx][i].get_inverse() + if inv == self._l[idx][i+x+1]: + # List of the indices of the qubits that are involved + # in command + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + for qb in sublist] + # List of the command indices corresponding to the position + # of this command on each qubit id + commandidcs = self._get_gate_indices(idx, i, qubitids) + erase = self._can_cancel_by_commutation(idx, qubitids, commandidcs, inv, self._apply_commutation) + if erase: + # Delete the inverse commands. Delete the later + # one first so the first index doesn't + # change before you delete it. + self._delete_command(idx, i+x+1) + self._delete_command(idx, i) + i = 0 + limit -= 2 break - command_i = self._l[idx][i] - next_command = self._l[idx][i+x+1] - #----------------------------------------------------------# - # See if next_command is commutable with this_command. # # - #----------------------------------------------------------# - if(command_i.is_commutable(next_command) == 1): - x=x+1 - continue - - #----------------------------------------------------------# - # See if next_command is part of a circuit which is # - # commutable with this_command. # - #----------------------------------------------------------# - new_x = 0 - if(command_i.is_commutable(next_command) == 2): - new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) - if(new_x>x): - x=new_x - self._i_x_com = True - continue - else: - self._i_x_com = False + try: + merged_command = self._l[idx][i].get_merged(self._l[idx][i+x+1]) + # determine index of this gate on all qubits + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + for qb in sublist] + commandidcs = self._get_gate_indices(idx, i, qubitids) + merge = self._can_merge_by_commutation(idx, qubitids, commandidcs, + merged_command, self._apply_commutation) + if merge: + # Delete command i+x+1 first because i+x+1 + # will not affect index of i + self._delete_command(idx, i+x+1) + self._replace_command(idx, i, merged_command) + i = 0 + limit -= 1 break + except NotMergeable: + # Unsuccessful in merging, see if gates are commutable + pass + + # If apply_commutation=False, then we want the optimizer to + # ignore commutation when optimizing + if not self._apply_commutation: + break + command_i = self._l[idx][i] + next_command = self._l[idx][i+x+1] + #----------------------------------------------------------# + # See if next_command is commutable with this_command. # # + #----------------------------------------------------------# + commutability_check = command_i.is_commutable(next_command) + if(commutability_check == 1): + x=x+1 + continue + + #----------------------------------------------------------# + # See if next_command is part of a circuit which is # + # commutable with this_command. # + #----------------------------------------------------------# + new_x = 0 + if(commutability_check == 2): + new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) + if(new_x>x): + x=new_x + continue + else: + break i += 1 # next iteration: look at next gate return limit diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index d7d40ae94..5a44a2459 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -264,11 +264,11 @@ def is_commutable(self, other): """ for gate in self._commutable_gates: if (other.__class__ == gate): - return 1 + return True else: # Default is to return False, including if # other gate and gate are identical - return 0 + return False class MatrixGate(BasicGate): diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 385191139..8eafeb94b 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -163,16 +163,16 @@ def is_commutable(self, other): True if the other command concerns the same qubits and the gates are commutable. """ - if (overlap(self.all_qubits, other.all_qubits)==0): + if not overlap(self.all_qubits, other.all_qubits): return 0 self._commutable_circuit_list = self.gate.get_commutable_circuit_list(len(self.control_qubits)) # If other gate may be part of a list which is # commutable with gate, return 2 for circuit in self._commutable_circuit_list: - if (other.gate.__class__ == circuit[0]._gate.__class__): + if type(other.gate) is type(circuit[0]._gate): return 2 else: - return self.gate.is_commutable(other.gate) + return int(self.gate.is_commutable(other.gate)) def get_merged(self, other): """ diff --git a/projectq/ops/_relative_command.py b/projectq/ops/_relative_command.py index 1dbab37e0..88d30c57b 100644 --- a/projectq/ops/_relative_command.py +++ b/projectq/ops/_relative_command.py @@ -107,15 +107,10 @@ def to_string(self, symbols=False): return self.gate.to_string(symbols) + " | " + qstring def equals(self, other): - if other is None: - return False - if (self.gate.__class__ != other.gate.__class__): - return False - if (self.relative_qubit_idcs != other.relative_qubit_idcs): - return False - if (self.relative_ctrl_idcs != other.relative_ctrl_idcs): - return False - if (self._gate.__class__ != self._gate.__class__): - return False + if ((type(self.gate) is type(other.gate)) + and (self.relative_qubit_idcs == other.relative_qubit_idcs) + and (self.relative_ctrl_idcs == other.relative_ctrl_idcs) + and (type(self._gate) is type(self._gate))): + return True else: - return True + return False From a51ca91a3e30807fd2eaa83dff3703dc97f21410 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Thu, 1 Apr 2021 19:25:43 +0100 Subject: [PATCH 04/16] Medium changes - Commutability enum - Commutation rules for X with Rx, Ph and SqrtX, plus tests in _optimize_test, _command_test and _gates_test --- projectq/cengines/_optimize.py | 6 ++-- projectq/cengines/_optimize_test.py | 43 +++++++++++++++++++---------- projectq/ops/_basics.py | 28 +++++++++++-------- projectq/ops/_basics_test.py | 11 +++++--- projectq/ops/_command.py | 19 ++++++++----- projectq/ops/_command_test.py | 14 ++++++++-- projectq/ops/_gates.py | 15 +++++++++- projectq/ops/_gates_test.py | 24 ++++++++++++++-- 8 files changed, 114 insertions(+), 46 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 13ff4fc7b..2450c14f6 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -15,10 +15,10 @@ """ Contains a local optimizer engine. """ - from copy import deepcopy as _deepcopy from projectq.cengines import LastEngineException, BasicEngine from projectq.ops import FlushGate, FastForwardingGate, NotMergeable, XGate +from projectq.ops._basics import Commutability class LocalOptimizer(BasicEngine): @@ -479,7 +479,7 @@ def _optimize(self, idx, lim=None): # See if next_command is commutable with this_command. # # #----------------------------------------------------------# commutability_check = command_i.is_commutable(next_command) - if(commutability_check == 1): + if(commutability_check == Commutability.COMMUTABLE.value): x=x+1 continue @@ -488,7 +488,7 @@ def _optimize(self, idx, lim=None): # commutable with this_command. # #----------------------------------------------------------# new_x = 0 - if(commutability_check == 2): + if(commutability_check == Commutability.MAYBE_COMMUTABLE.value): new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) if(new_x>x): x=new_x diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 7208ffaee..8a276e769 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -19,7 +19,7 @@ from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import (CNOT, H, Rx, Ry, Rz, Rxx, Ryy, Rzz, Measure, AllocateQubitGate, X, - FastForwardingGate, ClassicalInstructionGate, XGate) + FastForwardingGate, ClassicalInstructionGate, XGate, Ph, X, SqrtX) from projectq.setups import restrictedgateset, trapped_ion_decomposer from projectq.cengines import _optimize @@ -252,7 +252,18 @@ def test_local_optimizer_commutable_gates(): Rx(0.3) | qb2 Rx(0.2) | qb3 Rxx(0.2) | (qb3, qb2) - assert len(backend.received_commands) == 0 + X | qb3 + Ph(0.2) | qb3 + X | qb3 + Ph(0.1) | qb3 + X | qb3 + Rx(0.2) | qb3 + X | qb3 + X | qb3 + SqrtX | qb3 + X | qb3 + # All the Xs should cancel, because we have 3 pairs of Xs each + # separated by a gate which is commutable with X. eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -260,21 +271,23 @@ def test_local_optimizer_commutable_gates(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert len(received_commands) == 5 - assert received_commands[0].gate == Rx(0.9) + print(cmd) + assert len(received_commands) == 8 + assert received_commands[0].gate == Rxx(0.4) + # A check that we have the gates we expect. + assert received_commands[1].gate == Rx(0.2) + assert received_commands[2].gate == Rx(0.9) + assert received_commands[3].gate == Rxx(0.8) + assert received_commands[4].gate == Rx(0.3) + assert received_commands[5].gate == Ph(0.3) + assert received_commands[6].gate == Rx(0.2) + assert received_commands[7].gate == SqrtX + # A check the qubit ids on the final gates in the circuit + # are as we expect. # If this test doesn't succeed check that the Rxx # interchangeable qubits attribute is working. - assert received_commands[1].gate == Rxx(0.8) - assert received_commands[2].gate == Rxx(0.4) - assert received_commands[3].gate == Rx(0.3) - assert received_commands[4].gate == Rx(0.2) - assert received_commands[0].qubits[0][0].id == qb0[0].id - assert received_commands[1].qubits[0][0].id == qb0[0].id - assert received_commands[1].qubits[1][0].id == qb1[0].id - assert received_commands[2].qubits[0][0].id == qb2[0].id - assert received_commands[2].qubits[1][0].id == qb3[0].id - assert received_commands[3].qubits[0][0].id == qb2[0].id - assert received_commands[4].qubits[0][0].id == qb3[0].id + assert received_commands[0].qubits[0][0].id == qb2[0].id + assert received_commands[0].qubits[1][0].id == qb3[0].id def test_local_optimizer_commutable_circuit_Rz_example_1(): """ Example circuit where the Rzs should merge. """ diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 5a44a2459..b4c4d4d0b 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -36,7 +36,7 @@ import numpy as np from projectq.types import BasicQubit -from ._command import Command, apply_command +from ._command import Command, apply_command, Commutability import unicodedata @@ -126,12 +126,18 @@ def get_merged(self, other): """ raise NotMergeable("BasicGate: No get_merged() implemented.") + def get_commutable_gates(self): + return self._commutable_gates + def get_commutable_circuit_list(self, n=0): - """ Returns the list of commutable circuits associated with this gate. - + """ Args: n (int): The CNOT gate needs to be able to pass in parameter n in this method. + + Returns: + _commutable_circuit_list (list): the list of commutable circuits + associated with this gate. """ return self._commutable_circuit_list @@ -259,16 +265,16 @@ def is_commutable(self, other): other (Gate): The other gate. Returns: - 1, if the gates are commutable. - 0, if the gates are not commutable. + commutability (Commutability) : An enum which + indicates whether the next gate is commutable, + not commutable or maybe commutable. """ - for gate in self._commutable_gates: - if (other.__class__ == gate): - return True + commutable_gates = self.get_commutable_gates() + for gate in commutable_gates: + if type(other) is gate: + return Commutability.COMMUTABLE.value else: - # Default is to return False, including if - # other gate and gate are identical - return False + return Commutability.NOT_COMMUTABLE.value class MatrixGate(BasicGate): diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 5aa67659f..1d4c94c01 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -16,6 +16,9 @@ import math +import sys +sys.path.append(r'C:\Users\daisy\Documents\Code\ProjectQ') + import numpy as np import pytest @@ -347,10 +350,10 @@ def test_is_commutable(): gate1 = _basics.BasicRotationGate(math.pi) gate2 = _basics.MatrixGate() gate3 = _basics.BasicRotationGate(math.pi) - assert gate1.is_commutable(gate2) == False - assert gate1.is_commutable(gate3) == False + assert not gate1.is_commutable(gate2) + assert not gate1.is_commutable(gate3) gate4 = _gates.Rz(math.pi) gate5 = _gates.H gate6 = _metagates.C(NOT) - assert gate4.is_commutable(gate5) == 0 - assert gate4.is_commutable(gate6) == 0 \ No newline at end of file + assert not gate4.is_commutable(gate5) + assert not gate4.is_commutable(gate6) \ No newline at end of file diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 8eafeb94b..9f3222781 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -37,10 +37,11 @@ The command then gets sent to the MainEngine via the apply wrapper (apply_command). """ - +from enum import Enum from copy import deepcopy import projectq from projectq.types import WeakQubitRef, Qureg +from projectq.ops import _basics def apply_command(cmd): """ @@ -160,19 +161,18 @@ def is_commutable(self, other): other (Command): The other command. Returns: - True if the other command concerns the same qubits and - the gates are commutable. + Commutability value (int) : value of the commutability enum """ if not overlap(self.all_qubits, other.all_qubits): - return 0 + return Commutability.NOT_COMMUTABLE.value self._commutable_circuit_list = self.gate.get_commutable_circuit_list(len(self.control_qubits)) # If other gate may be part of a list which is - # commutable with gate, return 2 + # commutable with gate, return enum MAYBE_COMMUTABLE for circuit in self._commutable_circuit_list: if type(other.gate) is type(circuit[0]._gate): - return 2 + return Commutability.MAYBE_COMMUTABLE.value else: - return int(self.gate.is_commutable(other.gate)) + return self.gate.is_commutable(other.gate) def get_merged(self, other): """ @@ -361,3 +361,8 @@ def overlap(tuple1, tuple2): if element in flat_tuple2: n+=1 return n + +class Commutability(Enum): + NOT_COMMUTABLE = 0 + COMMUTABLE = 1 + MAYBE_COMMUTABLE = 2 \ No newline at end of file diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index c165a2bfd..b4daf9890 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -23,9 +23,10 @@ from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.meta import ComputeTag -from projectq.ops import BasicGate, Rx, NotMergeable, Rxx, Ry, H, Rz, CNOT +from projectq.ops import BasicGate, Rx, NotMergeable, Rxx, Ry, H, Rz, CNOT, X, SqrtX, Ph from projectq.types import Qubit, Qureg, WeakQubitRef from projectq.ops import _command, _basics +from projectq.ops._command import Commutability @pytest.fixture @@ -175,13 +176,22 @@ def test_command_is_commutable(main_engine): cmd6 = _command.Command(main_engine, Rz(0.2), (qubit1,)) cmd7 = _command.Command(main_engine, H, (qubit1,)) cmd8 = _command.Command(main_engine, CNOT, (qubit2,), qubit1) + cmd9 = _command.Command(main_engine, X, (qubit1,)) + cmd10 = _command.Command(main_engine, SqrtX, (qubit1,)) + cmd11 = _command.Command(main_engine, Ph(math.pi), (qubit1,)) assert not cmd1.is_commutable(cmd2) #Identical qubits, identical gate assert _command.overlap(cmd1.all_qubits, cmd3.all_qubits) == 0 assert not cmd1.is_commutable(cmd3) #Different qubits, same gate assert cmd3.is_commutable(cmd4) #Qubits in common, different but commutable gates assert not cmd4.is_commutable(cmd5) #Qubits in common, different, non-commutable gates - assert cmd6.is_commutable(cmd7) == 2 # Rz has a commutable circuit which starts with H + assert cmd6.is_commutable(cmd7) == Commutability.MAYBE_COMMUTABLE.value # Rz has a commutable circuit which starts with H assert not cmd7.is_commutable(cmd8) # H does not have a commutable circuit which starts with CNOT + assert cmd1.is_commutable(cmd9) # Rx commutes with X + assert cmd9.is_commutable(cmd1) + assert cmd10.is_commutable(cmd9) # SqrtX commutes with X + assert cmd9.is_commutable(cmd10) + assert cmd11.is_commutable(cmd9) # Ph commutes with X + assert cmd9.is_commutable(cmd11) def test_command_order_qubits(main_engine): diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 488ad5659..1f219691a 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -85,6 +85,10 @@ def __str__(self): def matrix(self): return np.matrix([[0, 1], [1, 0]]) + def get_commutable_gates(self): + self._commutable_gates = [Rx, Ph, SqrtXGate] + return self._commutable_gates + def get_commutable_circuit_list(self, n=0): """ Sets _commutable_circuit_list for C(NOT, n) where n is the number of controls @@ -161,6 +165,11 @@ def __str__(self): class SqrtXGate(BasicGate): """ Square-root X gate class """ + + def __init__(self): + BasicGate.__init__(self) + self._commutable_gates = [XGate,] + @property def matrix(self): return 0.5 * np.matrix([[1+1j, 1-1j], [1-1j, 1+1j]]) @@ -229,6 +238,10 @@ def __str__(self): class Ph(BasicPhaseGate): """ Phase gate (global phase) """ + def __init__(self, angle): + BasicPhaseGate.__init__(self, angle) + self._commutable_gates = [XGate,] + @property def matrix(self): return np.matrix([[cmath.exp(1j * self.angle), 0], @@ -240,7 +253,7 @@ class Rx(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) - self._commutable_gates = [Rxx,] + self._commutable_gates = [Rxx,XGate] @property def matrix(self): diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index cfec3b3f9..248dfc817 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -19,6 +19,9 @@ import numpy as np import pytest +import sys +sys.path.append(r'C:\Users\daisy\Documents\Code\ProjectQ') + from projectq import MainEngine from projectq.ops import (All, FlipBits, get_inverse, SelfInverseGate, BasicRotationGate, ClassicalInstructionGate, @@ -46,7 +49,14 @@ def test_x_gate(): assert np.array_equal(gate1.matrix, np.matrix([[0, 1], [1, 0]])) assert isinstance(_gates.X, _gates.XGate) assert isinstance(_gates.NOT, _gates.XGate) - assert gate1._commutable_circuit_list == [] + gate3 = _gates.X + gate4 = _gates.Rx(math.pi) + gate5 = _gates.Ph(math.pi) + gate6 = _gates.SqrtX + assert gate3.is_commutable(gate4) + assert gate3.is_commutable(gate5) + assert gate3.is_commutable(gate6) + assert gate3._commutable_circuit_list == [] cmd1=RelativeCommand(H,(0,)) cmd2=RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)) cmd3=RelativeCommand(H,(0,)) @@ -98,7 +108,10 @@ def test_sqrtx_gate(): assert np.array_equal(gate.matrix * gate.matrix, np.matrix([[0j, 1], [1, 0]])) assert isinstance(_gates.SqrtX, _gates.SqrtXGate) - + gate1 = _gates.SqrtX + gate2 = _gates.X + assert gate1.is_commutable(gate2) + assert gate2.is_commutable(gate1) def test_swap_gate(): gate = _gates.SwapGate() @@ -139,6 +152,7 @@ def test_rx(angle1, angle2): gate1 = _gates.Rx(angle1) gate2 = _gates.Rxx(angle2) gate3 = _gates.Ry(angle1) + gate4 = _gates.X expected_matrix = np.matrix([[math.cos(0.5 * angle1), -1j * math.sin(0.5 * angle1)], [-1j * math.sin(0.5 * angle1), @@ -147,6 +161,8 @@ def test_rx(angle1, angle2): assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) + assert gate1.is_commutable(gate4) + assert gate4.is_commutable(gate1) @pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, @@ -253,6 +269,7 @@ def test_rzz(angle1, angle2): def test_ph(angle): gate = _gates.Ph(angle) gate2 = _gates.Ph(angle + 2 * math.pi) + gate3 = _gates.X expected_matrix = np.matrix([[cmath.exp(1j * angle), 0], [0, cmath.exp(1j * angle)]]) assert gate.matrix.shape == expected_matrix.shape @@ -260,7 +277,8 @@ def test_ph(angle): assert gate2.matrix.shape == expected_matrix.shape assert np.allclose(gate2.matrix, expected_matrix) assert gate == gate2 - + assert gate.is_commutable(gate3) + assert gate3.is_commutable(gate) @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi]) def test_r(angle): From db22a1bfb9c9175a0e9f282f2973e62668304196 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Wed, 7 Apr 2021 16:00:55 +0100 Subject: [PATCH 05/16] New commutation rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added more commutable gates to the _commutable_gates list for gates: X, Y, Z, Rx, Ry, Rz, Rxx, Ryy, Rzz, SqrtX, Ph, S, T, R - Added parameterized unit tests to check these commutation rules are recognized by the LocalOptimizer - Minor stylistic changes - I haven’t added tests to check that S, T and SqrtX commute through their commuting partners because as far as I know they don’t cancel/merge with themselves, so there is no way of checking/no need for that functionality. --- projectq/cengines/_optimize.py | 9 +- projectq/cengines/_optimize_test.py | 125 ++++++++++++++++++---------- projectq/ops/_gates.py | 53 ++++++++---- projectq/ops/_relative_command.py | 11 +-- 4 files changed, 121 insertions(+), 77 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 2450c14f6..3a9b65994 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -227,12 +227,9 @@ def _can_cancel_by_commutation(self, idx, qubitids, commandidcs, inverse_command def _can_merge_by_commutation(self, idx, qubitids, commandidcs, merged_command, apply_commutation): """ - To determine whether mergeable commands should be merged - with one another. i.e. the commands between them are all - commutable for each qubit involved in the command. It does - not check for the situation where commands are separated by - a commutable list. However other parts of the optimizer - should find this situation. + Determines whether mergeable commands should be merged + with one another. i.e. the commands between the pair are all + commutable for each qubit involved in the command. Args: idx (int): qubit index diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 8a276e769..b066e1e62 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -19,7 +19,8 @@ from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import (CNOT, H, Rx, Ry, Rz, Rxx, Ryy, Rzz, Measure, AllocateQubitGate, X, - FastForwardingGate, ClassicalInstructionGate, XGate, Ph, X, SqrtX) + FastForwardingGate, ClassicalInstructionGate, XGate, Ph, X, + SqrtX, Y, Z, S, T, R) from projectq.setups import restrictedgateset, trapped_ion_decomposer from projectq.cengines import _optimize @@ -230,40 +231,86 @@ def test_local_optimizer_identity_gates(): assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(0.5) -def test_local_optimizer_commutable_gates(): - """ Test that inverse gates separated by two commutable gates - cancel successfully and that mergeable gates separated by - two commutable gates cancel successfully. +@pytest.mark.parametrize(["U", "Ru", "Ruu"], [[X, Rx, Rxx], [Y, Ry, Ryy], [Z, Rz, Rzz]]) +def test_local_optimizer_commutable_gates_parameterized_1(U, Ru, Ruu): + """ Iterate through gates of the X, Y, Z type and + check that they correctly commute with eachother and with Ph. """ local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - qb2 = eng.allocate_qubit() - qb3 = eng.allocate_qubit() + # Check U and commutes through Ru, Ruu. + # Check Ph commutes through U, Ru, Ruu. + U | qb0 + Ru(0.4) | qb0 + Ruu(0.4) | (qb0, qb1) + Ph(0.4) | qb0 + U | qb0 + # Check Ru commutes through U, Ruu, Ph + # (the first two Us should have cancelled already) + # We should now have a circuit: Ru(0.4), Ruu(0.4), Ph(0.4), U + U | qb0 + Ru(0.4) | qb0 + # Check Ruu commutes through U, Ru, Ph + # We should now have a circuit: Ru(0.8), Ruu(0.4), Ph(0.4), U + Ru(0.4) | qb0 + Ruu(0.4) | (qb0, qb1) + # Check Ph commutes through U, Ru, Ruu + # We should now have a circuit: Ru(0.8), Ruu(0.8), Ph(0.4), U, Ru(0.4) + Ruu(0.4) | (qb0, qb1) + Ph(0.4) | qb0 + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 4 + +@pytest.mark.parametrize("U", [Ph, Rz, R]) +@pytest.mark.parametrize("C", [Z, S, T]) +def test_local_optimizer_commutable_gates_parameterized_2(U, C): + """ Tests that the Rzz, Ph, Rz, R gates commute through S, T, Z.""" + local_optimizer = _optimize.LocalOptimizer(m=5) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rzz(0.4) | (qb0, qb1) + U(0.4) | qb0 + C | qb0 + U(0.4) | qb0 + Rzz(0.4) | (qb0, qb1) + eng.flush() + received_commands = [] + # Remove Allocate and Deallocate gates + for cmd in backend.received_commands: + if not (isinstance(cmd.gate, FastForwardingGate) or + isinstance(cmd.gate, ClassicalInstructionGate)): + received_commands.append(cmd) + assert len(received_commands) == 3 + #assert received_commands[0].gate == Ph(0.8) + +def test_local_optimizer_commutable_gates_SqrtX(): + """ Tests that the X, Rx, Rxx, Ph gates commute through SqrtX.""" + local_optimizer = _optimize.LocalOptimizer(m=5) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + X | qb0 Rx(0.4) | qb0 - Rx(-math.pi) | qb1 - Rxx(0.3) | (qb1, qb0) - Rxx(0.5) | (qb0, qb1) - Rx(math.pi) | qb1 - Rx(0.5) | qb0 - Rxx(0.2) | (qb2, qb3) - Rx(0.3) | qb2 - Rx(0.2) | qb3 - Rxx(0.2) | (qb3, qb2) - X | qb3 - Ph(0.2) | qb3 - X | qb3 - Ph(0.1) | qb3 - X | qb3 - Rx(0.2) | qb3 - X | qb3 - X | qb3 - SqrtX | qb3 - X | qb3 - # All the Xs should cancel, because we have 3 pairs of Xs each - # separated by a gate which is commutable with X. + Rxx(0.4) | (qb0, qb1) + SqrtX | qb0 + X | qb0 + Rx(0.4) | qb0 + Rxx(0.4) | (qb0, qb1) + Ph(0.4) | qb0 + SqrtX | qb0 + Ph(0.4) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -271,23 +318,7 @@ def test_local_optimizer_commutable_gates(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - print(cmd) - assert len(received_commands) == 8 - assert received_commands[0].gate == Rxx(0.4) - # A check that we have the gates we expect. - assert received_commands[1].gate == Rx(0.2) - assert received_commands[2].gate == Rx(0.9) - assert received_commands[3].gate == Rxx(0.8) - assert received_commands[4].gate == Rx(0.3) - assert received_commands[5].gate == Ph(0.3) - assert received_commands[6].gate == Rx(0.2) - assert received_commands[7].gate == SqrtX - # A check the qubit ids on the final gates in the circuit - # are as we expect. - # If this test doesn't succeed check that the Rxx - # interchangeable qubits attribute is working. - assert received_commands[0].qubits[0][0].id == qb2[0].id - assert received_commands[0].qubits[1][0].id == qb3[0].id + assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_Rz_example_1(): """ Example circuit where the Rzs should merge. """ @@ -730,4 +761,6 @@ def test_local_optimizer_apply_commutation_false(): assert received_commands[2].gate == Rz(0.2) assert received_commands[4].gate == Ry(0.1) assert received_commands[7].gate == Ry(0.2) - assert received_commands[10].gate == Rxx(0.1) \ No newline at end of file + assert received_commands[10].gate == Rxx(0.1) + +test_local_optimizer_commutable_gates_parameterized_1(X, Rx, Rxx) \ No newline at end of file diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 1f219691a..0c84ff178 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -86,7 +86,7 @@ def matrix(self): return np.matrix([[0, 1], [1, 0]]) def get_commutable_gates(self): - self._commutable_gates = [Rx, Ph, SqrtXGate] + self._commutable_gates = [Rx, Rxx, Ph, SqrtXGate] return self._commutable_gates def get_commutable_circuit_list(self, n=0): @@ -109,6 +109,7 @@ def get_commutable_circuit_list(self, n=0): class YGate(SelfInverseGate): """ Pauli-Y gate class """ + def __str__(self): return "Y" @@ -116,6 +117,10 @@ def __str__(self): def matrix(self): return np.matrix([[0, -1j], [1j, 0]]) + def get_commutable_gates(self): + self._commutable_gates = [Ry, Ryy, Ph] + return self._commutable_gates + #: Shortcut (instance of) :class:`projectq.ops.YGate` Y = YGate() @@ -128,6 +133,10 @@ def __str__(self): @property def matrix(self): return np.matrix([[1, 0], [0, -1]]) + + def get_commutable_gates(self): + self._commutable_gates = [Rz, Rzz, Ph, R] + return self._commutable_gates #: Shortcut (instance of) :class:`projectq.ops.ZGate` Z = ZGate() @@ -142,12 +151,14 @@ def matrix(self): def __str__(self): return "S" + def get_commutable_gates(self): + self._commutable_gates = [Rz, Rzz, Ph, R] + return self._commutable_gates #: Shortcut (instance of) :class:`projectq.ops.SGate` S = SGate() #: Inverse (and shortcut) of :class:`projectq.ops.SGate` Sdag = Sdagger = get_inverse(S) - class TGate(BasicGate): """ T gate class """ @property @@ -156,7 +167,10 @@ def matrix(self): def __str__(self): return "T" - + + def get_commutable_gates(self): + self._commutable_gates = [Rz, Rzz, Ph, R] + return self._commutable_gates #: Shortcut (instance of) :class:`projectq.ops.TGate` T = TGate() #: Inverse (and shortcut) of :class:`projectq.ops.TGate` @@ -166,10 +180,6 @@ def __str__(self): class SqrtXGate(BasicGate): """ Square-root X gate class """ - def __init__(self): - BasicGate.__init__(self) - self._commutable_gates = [XGate,] - @property def matrix(self): return 0.5 * np.matrix([[1+1j, 1-1j], [1-1j, 1+1j]]) @@ -180,6 +190,10 @@ def tex_str(self): def __str__(self): return "SqrtX" + def get_commutable_gates(self): + self._commutable_gates = [XGate, Rx, Rxx, Ph] + return self._commutable_gates + #: Shortcut (instance of) :class:`projectq.ops.SqrtXGate` SqrtX = SqrtXGate() @@ -238,22 +252,24 @@ def __str__(self): class Ph(BasicPhaseGate): """ Phase gate (global phase) """ - def __init__(self, angle): - BasicPhaseGate.__init__(self, angle) - self._commutable_gates = [XGate,] @property def matrix(self): return np.matrix([[cmath.exp(1j * self.angle), 0], [0, cmath.exp(1j * self.angle)]]) + def get_commutable_gates(self): + self._commutable_gates = [XGate, YGate, ZGate, Rx, Ry, Rz, + Rxx, Ryy, Rzz, SqrtXGate, SGate, TGate, R] + return self._commutable_gates + class Rx(BasicRotationGate): """ RotationX gate class """ def __init__(self, angle): BasicRotationGate.__init__(self, angle) - self._commutable_gates = [Rxx,XGate] + self._commutable_gates = [XGate, Rxx, Ph, SqrtXGate] @property def matrix(self): @@ -268,7 +284,7 @@ class Ry(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) - self._commutable_gates = [Ryy,] + self._commutable_gates = [YGate, Ryy, Ph] @property def matrix(self): @@ -283,7 +299,7 @@ class Rz(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) - self._commutable_gates = [Rzz,] + self._commutable_gates = [ZGate, Rzz, Ph, TGate, SGate, R] self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] @property @@ -298,7 +314,7 @@ class Rxx(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [Rx,] + self._commutable_gates = [XGate, Rx, Ph, SqrtXGate] @property def matrix(self): @@ -314,7 +330,7 @@ class Ryy(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [Ry,] + self._commutable_gates = [YGate, Ry, Ph] @property @@ -331,7 +347,7 @@ class Rzz(BasicRotationGate): def __init__(self, angle): BasicRotationGate.__init__(self, angle) self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [Rz,] + self._commutable_gates = [ZGate, Rz, TGate, SGate, Ph, R] @property def matrix(self): @@ -343,6 +359,11 @@ def matrix(self): class R(BasicPhaseGate): """ Phase-shift gate (equivalent to Rz up to a global phase) """ + + def __init__(self, angle): + BasicPhaseGate.__init__(self, angle) + self._commutable_gates = [ZGate, Rz, Rzz, Ph, SGate, TGate] + @property def matrix(self): return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) diff --git a/projectq/ops/_relative_command.py b/projectq/ops/_relative_command.py index 88d30c57b..5a946471b 100644 --- a/projectq/ops/_relative_command.py +++ b/projectq/ops/_relative_command.py @@ -61,12 +61,7 @@ class RelativeCommand(object): Attributes: gate: The gate class. - _gate: If RelativeCommand.gate = ControlledGate, _gate will - return the gate class on the target qubit. - e.g. if relative_command.gate = CNOT - (class = projectq.ops._metagates.ControlledGate) - then relative_command._gate = NOT - (class = projectq.ops._gates.XGate) + _gate: The true gate class if gate is a metagate. relative_qubit_idcs: Tuple of integers, representing the relative qubit idcs in a commutable_circuit. relative_ctrl_idcs: Tuple of integers, representing the @@ -76,9 +71,7 @@ def __init__(self, gate, relative_qubit_idcs, relative_ctrl_idcs=()): self.gate = gate self.relative_qubit_idcs = relative_qubit_idcs self.relative_ctrl_idcs = relative_ctrl_idcs - self._gate = gate - if (self.gate.__class__ == ControlledGate): - self._gate = self.gate._gate + self._gate = self.gate._gate if isinstance(self.gate, ControlledGate) else gate def __str__(self): return self.to_string() From 47c48e8a1e332afbc2ff29ad01a985b24290158e Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Tue, 13 Apr 2021 20:41:03 +0100 Subject: [PATCH 06/16] Medium changes - Commutability Enum changed to IntEnum. - Commutable circuit added to Ph and R, and commutable circuit tests in optimize_test parameterised to test R, Rz, Ph. --- projectq/cengines/_optimize.py | 4 +- projectq/cengines/_optimize_test.py | 99 ++++++++++++++++------------- projectq/ops/_basics.py | 4 +- projectq/ops/_command.py | 8 +-- projectq/ops/_gates.py | 15 ++++- 5 files changed, 77 insertions(+), 53 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 3a9b65994..5378976c7 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -476,7 +476,7 @@ def _optimize(self, idx, lim=None): # See if next_command is commutable with this_command. # # #----------------------------------------------------------# commutability_check = command_i.is_commutable(next_command) - if(commutability_check == Commutability.COMMUTABLE.value): + if(commutability_check == Commutability.COMMUTABLE): x=x+1 continue @@ -485,7 +485,7 @@ def _optimize(self, idx, lim=None): # commutable with this_command. # #----------------------------------------------------------# new_x = 0 - if(commutability_check == Commutability.MAYBE_COMMUTABLE.value): + if(commutability_check == Commutability.MAYBE_COMMUTABLE): new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) if(new_x>x): x=new_x diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index b066e1e62..e68b1e06f 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -320,7 +320,8 @@ def test_local_optimizer_commutable_gates_SqrtX(): received_commands.append(cmd) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_1(): +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_1(U): """ Example circuit where the Rzs should merge. """ # Rzs should merge local_optimizer = _optimize.LocalOptimizer(m=10) @@ -328,11 +329,11 @@ def test_local_optimizer_commutable_circuit_Rz_example_1(): eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb0 + U(0.2) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -340,22 +341,23 @@ def test_local_optimizer_commutable_circuit_Rz_example_1(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[0].gate == Rz(0.3) + assert received_commands[0].gate == U(0.3) assert len(received_commands) == 4 -def test_local_optimizer_commutable_circuit_Rz_example_2(): - """ Rzs shouldn't merge (Although in theory they should, +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_2(U): + """ Us shouldn't merge (Although in theory they should, this would require a new update.) """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb1 + U(0.1) | qb1 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb1 + U(0.2) | qb1 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -363,21 +365,22 @@ def test_local_optimizer_commutable_circuit_Rz_example_2(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[1].gate == Rz(0.1) + assert received_commands[1].gate == U(0.1) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_3(): - """ Rzs should not merge because they are operating on different qubits. """ +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_3(U): + """ Us should not merge because they are operating on different qubits. """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb1 + U(0.1) | qb1 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb0 + U(0.2) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -385,21 +388,22 @@ def test_local_optimizer_commutable_circuit_Rz_example_3(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[1].gate == Rz(0.1) + assert received_commands[1].gate == U(0.1) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_4(): - """Rzs shouldn't merge because they are operating on different qubits.""" +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_4(U): + """Us shouldn't merge because they are operating on different qubits.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb1 + U(0.2) | qb1 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -407,21 +411,22 @@ def test_local_optimizer_commutable_circuit_Rz_example_4(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[0].gate == Rz(0.1) + assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_5(): - """Rzs shouldn't merge because CNOT is the wrong orientation.""" +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_5(U): + """Us shouldn't merge because CNOT is the wrong orientation.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb0, qb1) H | qb0 - Rz(0.2) | qb0 + U(0.2) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -429,22 +434,26 @@ def test_local_optimizer_commutable_circuit_Rz_example_5(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[0].gate == Rz(0.1) + assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_6(): - """Rzs shouldn't merge (Although in theory they should, - this would require a new update.)""" +@pytest.mark.parametrize("U", [Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_6(U): + """Us shouldn't merge because the circuit is in the wrong + orientation. (In theory Rz, R would merge because there is + only a control between them but this would require a new update.) + Ph is missed from this example because Ph does commute through. + """ local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb1 CNOT | (qb0, qb1) H | qb1 - Rz(0.2) | qb0 + U(0.2) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -452,21 +461,23 @@ def test_local_optimizer_commutable_circuit_Rz_example_6(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[0].gate == Rz(0.1) + print(cmd) + assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 -def test_local_optimizer_commutable_circuit_Rz_example_7(): - """Rzs shouldn't merge. Second H on wrong qubit.""" +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_U_example_7(U): + """Us shouldn't merge. Second H on wrong qubit.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb1, qb0) H | qb1 - Rz(0.2) | qb0 + U(0.2) | qb0 eng.flush() received_commands = [] # Remove Allocate and Deallocate gates @@ -474,7 +485,7 @@ def test_local_optimizer_commutable_circuit_Rz_example_7(): if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) - assert received_commands[0].gate == Rz(0.1) + assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 def test_local_optimizer_commutable_circuit_CNOT_example_1(): @@ -666,7 +677,8 @@ def test_local_optimizer_commutable_circuit_CNOT_example_8(): assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate -def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_CNOT_and_U_example_1(U): """This example is to check everything works as expected when the commutable circuit is on later commands in the optimizer dictionary. The number of commmands should reduce from 10 to 7. """ @@ -676,11 +688,11 @@ def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb0 + U(0.2) | qb0 CNOT | (qb0, qb1) H | qb1 CNOT | (qb1, qb2) @@ -696,7 +708,8 @@ def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_1(): assert len(received_commands) == 7 assert received_commands[6].gate == H -def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_2(): +@pytest.mark.parametrize("U", [Ph, Rz, R]) +def test_local_optimizer_commutable_circuit_CNOT_and_U_example_2(U): """ This example is to check everything works as expected when the commutable circuit is on qubits 3, 4, 5. """ local_optimizer = _optimize.LocalOptimizer(m=10) @@ -707,11 +720,11 @@ def test_local_optimizer_commutable_circuit_CNOT_and_Rz_example_2(): qb2 = eng.allocate_qubit() qb3 = eng.allocate_qubit() qb4 = eng.allocate_qubit() - Rz(0.1) | qb0 + U(0.1) | qb0 H | qb0 CNOT | (qb1, qb0) H | qb0 - Rz(0.2) | qb0 + U(0.2) | qb0 CNOT | (qb2, qb3) H | qb3 CNOT | (qb3, qb4) @@ -761,6 +774,4 @@ def test_local_optimizer_apply_commutation_false(): assert received_commands[2].gate == Rz(0.2) assert received_commands[4].gate == Ry(0.1) assert received_commands[7].gate == Ry(0.2) - assert received_commands[10].gate == Rxx(0.1) - -test_local_optimizer_commutable_gates_parameterized_1(X, Rx, Rxx) \ No newline at end of file + assert received_commands[10].gate == Rxx(0.1) \ No newline at end of file diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index b4c4d4d0b..23958479f 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -272,9 +272,9 @@ def is_commutable(self, other): commutable_gates = self.get_commutable_gates() for gate in commutable_gates: if type(other) is gate: - return Commutability.COMMUTABLE.value + return Commutability.COMMUTABLE else: - return Commutability.NOT_COMMUTABLE.value + return Commutability.NOT_COMMUTABLE class MatrixGate(BasicGate): diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 9f3222781..0c946c223 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -37,7 +37,7 @@ The command then gets sent to the MainEngine via the apply wrapper (apply_command). """ -from enum import Enum +from enum import IntEnum from copy import deepcopy import projectq from projectq.types import WeakQubitRef, Qureg @@ -164,13 +164,13 @@ def is_commutable(self, other): Commutability value (int) : value of the commutability enum """ if not overlap(self.all_qubits, other.all_qubits): - return Commutability.NOT_COMMUTABLE.value + return Commutability.NOT_COMMUTABLE self._commutable_circuit_list = self.gate.get_commutable_circuit_list(len(self.control_qubits)) # If other gate may be part of a list which is # commutable with gate, return enum MAYBE_COMMUTABLE for circuit in self._commutable_circuit_list: if type(other.gate) is type(circuit[0]._gate): - return Commutability.MAYBE_COMMUTABLE.value + return Commutability.MAYBE_COMMUTABLE else: return self.gate.is_commutable(other.gate) @@ -362,7 +362,7 @@ def overlap(tuple1, tuple2): n+=1 return n -class Commutability(Enum): +class Commutability(IntEnum): NOT_COMMUTABLE = 0 COMMUTABLE = 1 MAYBE_COMMUTABLE = 2 \ No newline at end of file diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 0c84ff178..37133937e 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -62,6 +62,14 @@ from ._relative_command import RelativeCommand from projectq.types import BasicQubit +# For the gates: XGate, YGate, ZGate, SGate, TGate, SqrtXGate, Ph +# the commutable gates are defined within the get method +# (as opposed to the init method) because some of the commutable +# gates haven't been defined when the gate is initialised. To change +# this, we would need to look into defining commutation rules after +# all gates are defined. + + class HGate(SelfInverseGate): """ Hadamard gate class """ def __str__(self): @@ -253,6 +261,10 @@ def __str__(self): class Ph(BasicPhaseGate): """ Phase gate (global phase) """ + def __init__(self, angle): + BasicPhaseGate.__init__(self, angle) + self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + @property def matrix(self): return np.matrix([[cmath.exp(1j * self.angle), 0], @@ -363,7 +375,8 @@ class R(BasicPhaseGate): def __init__(self, angle): BasicPhaseGate.__init__(self, angle) self._commutable_gates = [ZGate, Rz, Rzz, Ph, SGate, TGate] - + self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + @property def matrix(self): return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) From dbc219e246d4d47af5ccbae64cd35a8b13bf8555 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:06:02 +0200 Subject: [PATCH 07/16] Move commutation relation definition outside of gate classes --- projectq/ops/_basics.py | 41 +++++++++----- projectq/ops/_command.py | 2 +- projectq/ops/_gates.py | 103 +++++++++++------------------------- projectq/ops/_gates_test.py | 2 +- 4 files changed, 61 insertions(+), 87 deletions(-) diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 23958479f..d48a30aff 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -62,7 +62,25 @@ class NotInvertible(Exception): pass -class BasicGate(object): +class BasicGateMeta(type): + """ + Meta class for all gates; mainly used to ensure that all gate classes have some basic class variable defined. + """ + def __new__(cls, name, bases, attrs): + + def get_commutable_gates(self): + return self._commutable_gates + + return super(BasicGateMeta, cls).__new__(cls, name, bases, {**attrs, + get_commutable_gates.__name__: get_commutable_gates, + '_commutable_gates': set()}) + + +class BasicGate(metaclass=BasicGateMeta): + """ + A list of gates that commute with this gate class + """ + """ Base class of all gates. (Don't use it directly but derive from it) """ @@ -101,8 +119,6 @@ def __init__(self): self.set_interchangeable_qubit_indices([[0,1],[2,3,4]]) """ self.interchangeable_qubit_indices = [] - self._commutable_gates = [] - self._commutable_circuit_list = [] def get_inverse(self): """ @@ -127,19 +143,19 @@ def get_merged(self, other): raise NotMergeable("BasicGate: No get_merged() implemented.") def get_commutable_gates(self): - return self._commutable_gates + return [] def get_commutable_circuit_list(self, n=0): - """ - Args: - n (int): The CNOT gate needs to be able to pass in parameter n in - this method. + """ + Args: + n (int): The CNOT gate needs to be able to pass in parameter n in + this method. Returns: - _commutable_circuit_list (list): the list of commutable circuits - associated with this gate. + _commutable_circuit_list (list): the list of commutable circuits + associated with this gate. """ - return self._commutable_circuit_list + return [] @staticmethod def make_tuple_of_qureg(qubits): @@ -269,8 +285,7 @@ def is_commutable(self, other): indicates whether the next gate is commutable, not commutable or maybe commutable. """ - commutable_gates = self.get_commutable_gates() - for gate in commutable_gates: + for gate in self.get_commutable_gates(): if type(other) is gate: return Commutability.COMMUTABLE else: diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 0c946c223..f138940a6 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -365,4 +365,4 @@ def overlap(tuple1, tuple2): class Commutability(IntEnum): NOT_COMMUTABLE = 0 COMMUTABLE = 1 - MAYBE_COMMUTABLE = 2 \ No newline at end of file + MAYBE_COMMUTABLE = 2 diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 37133937e..126f02706 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -62,13 +62,6 @@ from ._relative_command import RelativeCommand from projectq.types import BasicQubit -# For the gates: XGate, YGate, ZGate, SGate, TGate, SqrtXGate, Ph -# the commutable gates are defined within the get method -# (as opposed to the init method) because some of the commutable -# gates haven't been defined when the gate is initialised. To change -# this, we would need to look into defining commutation rules after -# all gates are defined. - class HGate(SelfInverseGate): """ Hadamard gate class """ @@ -93,10 +86,6 @@ def __str__(self): def matrix(self): return np.matrix([[0, 1], [1, 0]]) - def get_commutable_gates(self): - self._commutable_gates = [Rx, Rxx, Ph, SqrtXGate] - return self._commutable_gates - def get_commutable_circuit_list(self, n=0): """ Sets _commutable_circuit_list for C(NOT, n) where n is the number of controls @@ -107,8 +96,7 @@ def get_commutable_circuit_list(self, n=0): # i.e. this is a CNOT gate (one control) # We define the qubit with the NOT as qb0, the qubit with # the control as qb1, then all next qbs are labelled 2,3 etc. - commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)), RelativeCommand(H,(0,))]] - return commutable_circuit_list + return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)), RelativeCommand(H,(0,))]] else: return [] # don't change _commutable_circuit_list @@ -125,10 +113,6 @@ def __str__(self): def matrix(self): return np.matrix([[0, -1j], [1j, 0]]) - def get_commutable_gates(self): - self._commutable_gates = [Ry, Ryy, Ph] - return self._commutable_gates - #: Shortcut (instance of) :class:`projectq.ops.YGate` Y = YGate() @@ -141,10 +125,6 @@ def __str__(self): @property def matrix(self): return np.matrix([[1, 0], [0, -1]]) - - def get_commutable_gates(self): - self._commutable_gates = [Rz, Rzz, Ph, R] - return self._commutable_gates #: Shortcut (instance of) :class:`projectq.ops.ZGate` Z = ZGate() @@ -159,9 +139,7 @@ def matrix(self): def __str__(self): return "S" - def get_commutable_gates(self): - self._commutable_gates = [Rz, Rzz, Ph, R] - return self._commutable_gates + #: Shortcut (instance of) :class:`projectq.ops.SGate` S = SGate() #: Inverse (and shortcut) of :class:`projectq.ops.SGate` @@ -175,10 +153,8 @@ def matrix(self): def __str__(self): return "T" - - def get_commutable_gates(self): - self._commutable_gates = [Rz, Rzz, Ph, R] - return self._commutable_gates + + #: Shortcut (instance of) :class:`projectq.ops.TGate` T = TGate() #: Inverse (and shortcut) of :class:`projectq.ops.TGate` @@ -198,9 +174,6 @@ def tex_str(self): def __str__(self): return "SqrtX" - def get_commutable_gates(self): - self._commutable_gates = [XGate, Rx, Rxx, Ph] - return self._commutable_gates #: Shortcut (instance of) :class:`projectq.ops.SqrtXGate` SqrtX = SqrtXGate() @@ -261,28 +234,15 @@ def __str__(self): class Ph(BasicPhaseGate): """ Phase gate (global phase) """ - def __init__(self, angle): - BasicPhaseGate.__init__(self, angle) - self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] - @property def matrix(self): return np.matrix([[cmath.exp(1j * self.angle), 0], [0, cmath.exp(1j * self.angle)]]) - def get_commutable_gates(self): - self._commutable_gates = [XGate, YGate, ZGate, Rx, Ry, Rz, - Rxx, Ryy, Rzz, SqrtXGate, SGate, TGate, R] - return self._commutable_gates - class Rx(BasicRotationGate): """ RotationX gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self._commutable_gates = [XGate, Rxx, Ph, SqrtXGate] - @property def matrix(self): return np.matrix([[math.cos(0.5 * self.angle), @@ -294,10 +254,6 @@ def matrix(self): class Ry(BasicRotationGate): """ RotationY gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self._commutable_gates = [YGate, Ryy, Ph] - @property def matrix(self): return np.matrix([[math.cos(0.5 * self.angle), @@ -309,10 +265,14 @@ def matrix(self): class Rz(BasicRotationGate): """ RotationZ gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self._commutable_gates = [ZGate, Rzz, Ph, TGate, SGate, R] - self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + def get_commutable_circuit_list(self, n=0): + """ Sets _commutable_circuit_list for C(NOT, n) where + n is the number of controls + + Args: + n (int): The number of controls on this gate. """ + + return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] @property def matrix(self): @@ -323,11 +283,6 @@ def matrix(self): class Rxx(BasicRotationGate): """ RotationXX gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [XGate, Rx, Ph, SqrtXGate] - @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, -1j*cmath.sin(.5 * self.angle)], @@ -339,12 +294,6 @@ def matrix(self): class Ryy(BasicRotationGate): """ RotationYY gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [YGate, Ry, Ph] - - @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, 1j*cmath.sin(.5 * self.angle)], @@ -356,11 +305,6 @@ def matrix(self): class Rzz(BasicRotationGate): """ RotationZZ gate class """ - def __init__(self, angle): - BasicRotationGate.__init__(self, angle) - self.interchangeable_qubit_indices = [[0, 1]] - self._commutable_gates = [ZGate, Rz, TGate, SGate, Ph, R] - @property def matrix(self): return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0, 0, 0], @@ -372,10 +316,13 @@ def matrix(self): class R(BasicPhaseGate): """ Phase-shift gate (equivalent to Rz up to a global phase) """ - def __init__(self, angle): - BasicPhaseGate.__init__(self, angle) - self._commutable_gates = [ZGate, Rz, Rzz, Ph, SGate, TGate] - self._commutable_circuit_list = [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + def get_commutable_circuit_list(self, n=0): + """ Sets _commutable_circuit_list for C(NOT, n) where + n is the number of controls + + Args: + n (int): The number of controls on this gate. """ + return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] @property def matrix(self): @@ -532,3 +479,15 @@ def __eq__(self, other): def __hash__(self): return hash(self.__str__()) + +# ============================================================================== +# Define commutation relations + +def set_commutation_relations(commuting_gates): + for klass in commuting_gates: + klass._commutable_gates.update(commuting_gates) + klass._commutable_gates.discard(klass) + +set_commutation_relations([XGate, SqrtXGate, Rx, Rxx, Ph]) +set_commutation_relations([YGate, Ry, Ryy, Ph]) +set_commutation_relations([ZGate, SGate, TGate, Rz, Rzz, Ph, R]) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 248dfc817..e312fc394 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -56,7 +56,7 @@ def test_x_gate(): assert gate3.is_commutable(gate4) assert gate3.is_commutable(gate5) assert gate3.is_commutable(gate6) - assert gate3._commutable_circuit_list == [] + assert gate3.get_commutable_circuit_list() == [] cmd1=RelativeCommand(H,(0,)) cmd2=RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)) cmd3=RelativeCommand(H,(0,)) From 42d7d2f2ffa1619b8daad7511da1c67e3a1c180f Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:13:53 +0200 Subject: [PATCH 08/16] Fix tests for rotation gates --- projectq/ops/_gates_test.py | 302 ++++++++++++++++++++++-------------- 1 file changed, 182 insertions(+), 120 deletions(-) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index e312fc394..bcd075ece 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -20,12 +20,21 @@ import pytest import sys -sys.path.append(r'C:\Users\daisy\Documents\Code\ProjectQ') + +sys.path.append(r"C:\Users\daisy\Documents\Code\ProjectQ") from projectq import MainEngine -from projectq.ops import (All, FlipBits, get_inverse, SelfInverseGate, - BasicRotationGate, ClassicalInstructionGate, - FastForwardingGate, BasicGate, Measure) +from projectq.ops import ( + All, + FlipBits, + get_inverse, + SelfInverseGate, + BasicRotationGate, + ClassicalInstructionGate, + FastForwardingGate, + BasicGate, + Measure, +) from projectq.ops import _gates from projectq.ops import CNOT, H, C, NOT @@ -36,8 +45,9 @@ def test_h_gate(): gate = _gates.HGate() assert gate == gate.get_inverse() assert str(gate) == "H" - assert np.array_equal(gate.matrix, - 1. / math.sqrt(2) * np.matrix([[1, 1], [1, -1]])) + assert np.array_equal( + gate.matrix, 1.0 / math.sqrt(2) * np.matrix([[1, 1], [1, -1]]) + ) assert isinstance(_gates.H, _gates.HGate) @@ -57,10 +67,12 @@ def test_x_gate(): assert gate3.is_commutable(gate5) assert gate3.is_commutable(gate6) assert gate3.get_commutable_circuit_list() == [] - cmd1=RelativeCommand(H,(0,)) - cmd2=RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)) - cmd3=RelativeCommand(H,(0,)) - correct_commutable_circuit_list=[[cmd1,cmd2,cmd3],] + cmd1 = RelativeCommand(H, (0,)) + cmd2 = RelativeCommand(C(NOT), (2,), relative_ctrl_idcs=(0,)) + cmd3 = RelativeCommand(H, (0,)) + correct_commutable_circuit_list = [ + [cmd1, cmd2, cmd3], + ] assert gate2.commutable_circuit_list == correct_commutable_circuit_list @@ -92,9 +104,9 @@ def test_s_gate(): def test_t_gate(): gate = _gates.TGate() assert str(gate) == "T" - assert np.array_equal(gate.matrix, - np.matrix([[1, 0], - [0, cmath.exp(1j * cmath.pi / 4)]])) + assert np.array_equal( + gate.matrix, np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]]) + ) assert isinstance(_gates.T, _gates.TGate) assert isinstance(_gates.Tdag, type(get_inverse(gate))) assert isinstance(_gates.Tdagger, type(get_inverse(gate))) @@ -103,24 +115,25 @@ def test_t_gate(): def test_sqrtx_gate(): gate = _gates.SqrtXGate() assert str(gate) == "SqrtX" - assert np.array_equal(gate.matrix, np.matrix([[0.5 + 0.5j, 0.5 - 0.5j], - [0.5 - 0.5j, 0.5 + 0.5j]])) - assert np.array_equal(gate.matrix * gate.matrix, - np.matrix([[0j, 1], [1, 0]])) + assert np.array_equal( + gate.matrix, np.matrix([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]]) + ) + assert np.array_equal(gate.matrix * gate.matrix, np.matrix([[0j, 1], [1, 0]])) assert isinstance(_gates.SqrtX, _gates.SqrtXGate) gate1 = _gates.SqrtX gate2 = _gates.X assert gate1.is_commutable(gate2) assert gate2.is_commutable(gate1) + def test_swap_gate(): gate = _gates.SwapGate() assert gate == gate.get_inverse() assert str(gate) == "Swap" assert gate.interchangeable_qubit_indices == [[0, 1]] - assert np.array_equal(gate.matrix, - np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], - [0, 0, 0, 1]])) + assert np.array_equal( + gate.matrix, np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) + ) assert isinstance(_gates.Swap, _gates.SwapGate) @@ -128,13 +141,18 @@ def test_sqrtswap_gate(): sqrt_gate = _gates.SqrtSwapGate() swap_gate = _gates.SwapGate() assert str(sqrt_gate) == "SqrtSwap" - assert np.array_equal(sqrt_gate.matrix * sqrt_gate.matrix, - swap_gate.matrix) - assert np.array_equal(sqrt_gate.matrix, - np.matrix([[1, 0, 0, 0], - [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], - [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], - [0, 0, 0, 1]])) + assert np.array_equal(sqrt_gate.matrix * sqrt_gate.matrix, swap_gate.matrix) + assert np.array_equal( + sqrt_gate.matrix, + np.matrix( + [ + [1, 0, 0, 0], + [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], + [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], + [0, 0, 0, 1], + ] + ), + ) assert isinstance(_gates.SqrtSwap, _gates.SqrtSwapGate) @@ -144,121 +162,155 @@ def test_engangle_gate(): assert isinstance(_gates.Entangle, _gates.EntangleGate) -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_rx(angle1, angle2): +def test_rx(angle): + gate = _gates.Rx(angle) + expected_matrix = np.matrix( + [ + [math.cos(0.5 * angle), -1j * math.sin(0.5 * angle)], + [-1j * math.sin(0.5 * angle), math.cos(0.5 * angle)], + ] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_rx_commutation(angle1, angle2): gate1 = _gates.Rx(angle1) gate2 = _gates.Rxx(angle2) gate3 = _gates.Ry(angle1) gate4 = _gates.X - expected_matrix = np.matrix([[math.cos(0.5 * angle1), - -1j * math.sin(0.5 * angle1)], - [-1j * math.sin(0.5 * angle1), - math.cos(0.5 * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate1.is_commutable(gate4) assert gate4.is_commutable(gate1) -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_ry(angle1, angle2): +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_ry(angle): + gate = _gates.Ry(angle) + expected_matrix = np.matrix( + [ + [math.cos(0.5 * angle), -math.sin(0.5 * angle)], + [math.sin(0.5 * angle), math.cos(0.5 * angle)], + ] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_ry_commutation(angle1, angle2): gate1 = _gates.Ry(angle1) gate2 = _gates.Ryy(angle2) gate3 = _gates.Rz(angle1) - expected_matrix = np.matrix([[math.cos(0.5 * angle1), - -math.sin(0.5 * angle1)], - [math.sin(0.5 * angle1), - math.cos(0.5 * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_rz(angle1, angle2): +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_rz(angle): + gate = _gates.Rz(angle) + expected_matrix = np.matrix( + [[cmath.exp(-0.5 * 1j * angle), 0], [0, cmath.exp(0.5 * 1j * angle)]] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_rz_commutation(angle1, angle2): # At the gate level is_commutable can only return 0 or 1 # to indicate whether the two gates are commutable - # At the command level is_commutable can return 2, to + # At the command level is_commutable can return 2, to # to indicate a possible commutable_circuit gate1 = _gates.Rz(angle1) gate2 = _gates.Rzz(angle2) gate3 = _gates.Rx(angle1) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle1), 0], - [0, cmath.exp(.5 * 1j * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_rxx(angle1, angle2): + +@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) + expected_matrix = np.matrix( + [ + [cmath.cos(0.5 * angle), 0, 0, -1j * cmath.sin(0.5 * angle)], + [0, cmath.cos(0.5 * angle), -1j * cmath.sin(0.5 * angle), 0], + [0, -1j * cmath.sin(0.5 * angle), cmath.cos(0.5 * angle), 0], + [-1j * cmath.sin(0.5 * angle), 0, 0, cmath.cos(0.5 * angle)], + ] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_rxx_commutation(angle1, angle2): gate1 = _gates.Rxx(angle1) gate2 = _gates.Rx(angle2) gate3 = _gates.Ry(angle1) gate4 = _gates.Rxx(0.0) - expected_matrix = np.matrix([[cmath.cos(.5 * angle1), 0, 0, -1j * cmath.sin(.5 * angle1)], - [0, cmath.cos(.5 * angle1), -1j * cmath.sin(.5 * angle1), 0], - [0, -1j * cmath.sin(.5 * angle1), cmath.cos(.5 * angle1), 0], - [-1j * cmath.sin(.5 * angle1), 0, 0, cmath.cos(.5 * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate4.is_identity() - assert gate1.interchangeable_qubit_indices == [[0, 1]] -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_ryy(angle1, angle2): +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_ryy(angle): + gate = _gates.Ryy(angle) + expected_matrix = np.matrix( + [ + [cmath.cos(0.5 * angle), 0, 0, 1j * cmath.sin(0.5 * angle)], + [0, cmath.cos(0.5 * angle), -1j * cmath.sin(0.5 * angle), 0], + [0, -1j * cmath.sin(0.5 * angle), cmath.cos(0.5 * angle), 0], + [1j * cmath.sin(0.5 * angle), 0, 0, cmath.cos(0.5 * angle)], + ] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_ryy_commutation(angle1, angle2): gate1 = _gates.Ryy(angle1) gate2 = _gates.Ry(angle2) gate3 = _gates.Rx(angle1) gate4 = _gates.Ryy(0.0) - expected_matrix = np.matrix([[cmath.cos(.5 * angle1), 0, 0, 1j * cmath.sin(.5 * angle1)], - [0, cmath.cos(.5 * angle1), -1j * cmath.sin(.5 * angle1), 0], - [0, -1j * cmath.sin(.5 * angle1), cmath.cos(.5 * angle1), 0], - [ 1j * cmath.sin(.5 * angle1), 0, 0, cmath.cos(.5 * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate4.is_identity() assert gate1.interchangeable_qubit_indices == [[0, 1]] -@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) -@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, - 4 * math.pi]) -def test_rzz(angle1, angle2): +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +def test_rzz(angle): + gate = _gates.Rzz(angle) + expected_matrix = np.matrix( + [ + [cmath.exp(-0.5 * 1j * angle), 0, 0, 0], + [0, cmath.exp(0.5 * 1j * angle), 0, 0], + [0, 0, cmath.exp(0.5 * 1j * angle), 0], + [0, 0, 0, cmath.exp(-0.5 * 1j * angle)], + ] + ) + assert gate.matrix.shape == expected_matrix.shape + assert np.allclose(gate.matrix, expected_matrix) + + +@pytest.mark.parametrize("angle1", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) +@pytest.mark.parametrize("angle2", [4.1, 2.1, 0.2, 0, 2 * math.pi, 4 * math.pi]) +def test_rzz_commutation(angle1, angle2): gate1 = _gates.Rzz(angle1) gate2 = _gates.Rz(angle2) gate3 = _gates.Ry(angle1) gate4 = _gates.Rzz(0.0) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle1), 0, 0, 0], - [0, cmath.exp( .5 * 1j * angle1), 0, 0], - [0, 0, cmath.exp( .5 * 1j * angle1), 0], - [0, 0, 0, cmath.exp(-.5 * 1j * angle1)]]) - assert gate1.matrix.shape == expected_matrix.shape - assert np.allclose(gate1.matrix, expected_matrix) assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate4.is_identity() @@ -269,16 +321,26 @@ def test_rzz(angle1, angle2): def test_ph(angle): gate = _gates.Ph(angle) gate2 = _gates.Ph(angle + 2 * math.pi) - gate3 = _gates.X - expected_matrix = np.matrix([[cmath.exp(1j * angle), 0], - [0, cmath.exp(1j * angle)]]) + expected_matrix = np.matrix( + [[cmath.exp(1j * angle), 0], [0, cmath.exp(1j * angle)]] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) assert gate2.matrix.shape == expected_matrix.shape assert np.allclose(gate2.matrix, expected_matrix) assert gate == gate2 - assert gate.is_commutable(gate3) - assert gate3.is_commutable(gate) + + +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi]) +@pytest.mark.parametrize( + "other_gate", + [_gates.X, _gates.Y, _gates.Z, _gates.Rx(1), _gates.Ry(1), _gates.Rz(1), _gates.R(1)], +) +def test_ph_commutation(angle, other_gate): + gate = _gates.Ph(angle) + assert gate.is_commutable(other_gate) + assert other_gate.is_commutable(gate) + @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi]) def test_r(angle): @@ -352,18 +414,18 @@ def test_error_on_tuple_input(): flip_bits_testdata = [ - ([0, 1, 0, 1], '0101'), - ([1, 0, 1, 0], '1010'), - ([False, True, False, True], '0101'), - ('0101', '0101'), - ('1111', '1111'), - ('0000', '0000'), - (8, '0001'), - (11, '1101'), - (1, '1000'), - (-1, '1111'), - (-2, '0111'), - (-3, '1011'), + ([0, 1, 0, 1], "0101"), + ([1, 0, 1, 0], "1010"), + ([False, True, False, True], "0101"), + ("0101", "0101"), + ("1111", "1111"), + ("0000", "0000"), + (8, "0001"), + (11, "1101"), + (1, "1000"), + (-1, "1111"), + (-2, "0111"), + (-3, "1011"), ] @@ -373,7 +435,7 @@ def test_simulator_flip_bits(bits_to_flip, result): qubits = eng.allocate_qureg(4) FlipBits(bits_to_flip) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability(result, qubits)) == 1. + assert pytest.approx(eng.backend.get_probability(result, qubits)) == 1.0 All(Measure) | qubits @@ -381,26 +443,26 @@ def test_flip_bits_can_be_applied_to_various_qubit_qureg_formats(): eng = MainEngine() qubits = eng.allocate_qureg(4) eng.flush() - assert pytest.approx(eng.backend.get_probability('0000', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0000", qubits)) == 1.0 FlipBits([0, 1, 1, 0]) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0110", qubits)) == 1.0 FlipBits([1]) | qubits[0] eng.flush() - assert pytest.approx(eng.backend.get_probability('1110', qubits)) == 1. - FlipBits([1]) | (qubits[0], ) + assert pytest.approx(eng.backend.get_probability("1110", qubits)) == 1.0 + FlipBits([1]) | (qubits[0],) eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0110", qubits)) == 1.0 FlipBits([1, 1]) | [qubits[0], qubits[1]] eng.flush() - assert pytest.approx(eng.backend.get_probability('1010', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("1010", qubits)) == 1.0 FlipBits(-1) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability('0101', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0101", qubits)) == 1.0 FlipBits(-4) | [qubits[0], qubits[1], qubits[2], qubits[3]] eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0110", qubits)) == 1.0 FlipBits(2) | [qubits[0]] + [qubits[1], qubits[2]] eng.flush() - assert pytest.approx(eng.backend.get_probability('0010', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability("0010", qubits)) == 1.0 All(Measure) | qubits From a0e575cb6f958beffcab70520a63a99dda7f9c86 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:24:52 +0200 Subject: [PATCH 09/16] Remove local path from file --- projectq/ops/_gates_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index bcd075ece..401a72ef3 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -19,9 +19,6 @@ import numpy as np import pytest -import sys - -sys.path.append(r"C:\Users\daisy\Documents\Code\ProjectQ") from projectq import MainEngine from projectq.ops import ( From f5b1199bb811f4fa62e8a95fc7f824efbf7f4e12 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:28:24 +0200 Subject: [PATCH 10/16] Fix typos --- projectq/ops/_gates_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 401a72ef3..ae41a1ab3 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -159,6 +159,7 @@ def test_engangle_gate(): assert isinstance(_gates.Entangle, _gates.EntangleGate) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rx(angle): gate = _gates.Rx(angle) expected_matrix = np.matrix( @@ -283,7 +284,6 @@ def test_ryy_commutation(angle1, angle2): assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate4.is_identity() - assert gate1.interchangeable_qubit_indices == [[0, 1]] @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) @@ -311,7 +311,6 @@ def test_rzz_commutation(angle1, angle2): assert gate1.is_commutable(gate2) assert not gate1.is_commutable(gate3) assert gate4.is_identity() - assert gate1.interchangeable_qubit_indices == [[0, 1]] @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi]) From 063bc189535ca56bcb13cc8a7206f87d33f2fd23 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:35:22 +0200 Subject: [PATCH 11/16] Fix test failure due to new metaclass for BasicGate --- projectq/cengines/_replacer/_decomposition_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index d742f24c5..fa7feb329 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from projectq.ops import BasicGate +from projectq.ops._basics import BasicGate, BasicGateMeta class ThisIsNotAGateClassError(TypeError): @@ -61,7 +61,7 @@ def __init__(self, raise ThisIsNotAGateClassError( "gate_class is a gate instance instead of a type of BasicGate." "\nDid you pass in someGate instead of someGate.__class__?") - if gate_class == type.__class__: + if gate_class in (type.__class__, BasicGateMeta): raise ThisIsNotAGateClassError( "gate_class is type.__class__ instead of a type of BasicGate." "\nDid you pass in GateType.__class__ instead of GateType?") From dd96ae568ee55c61aa09a602928204c99cadae08 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:42:56 +0200 Subject: [PATCH 12/16] Fix test failure due to the optimiser able to merge commuting gates --- projectq/setups/trapped_ion_decomposer_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index 23b6485c6..4f92886ef 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -129,13 +129,14 @@ def test_chooser_Ry_reducer(): # Including the Allocate, Measure and Flush commands, this would result in # 13 commands. # - # Using the chooser_Rx_reducer you get 10 commands, since you now have 4 - # single qubit gates and 1 two qubit gate. + # Using the chooser_Rx_reducer you get 9 commands, since you now have 4 + # single qubit gates and 1 two qubit gate (plus the new optimiser can merge + # commuting gates) for engine_list, count in [(restrictedgateset.get_engine_list( one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx, )), 13), - (get_engine_list(), 11)]: + (get_engine_list(), 10)]: backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list, verbose=True) From e10e64d94ece58b800e81bd5832676326de78026 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 19:58:29 +0200 Subject: [PATCH 13/16] Re-add interchangeable indices for Rxx, Ryy and Rzz - Also fix some errors in the tests where Rxx, Ryy and Rzz were assumed to be single-qubit gates - Potential API break for users: Rxx(1.0) | qubit1 + qubit2 was considered valid and now should be replaced with: Rxx(1.0) | (qubit1, qubit2) --- projectq/backends/_aqt/_aqt_test.py | 15 +++++++++++++-- projectq/backends/_resource_test.py | 2 +- projectq/cengines/_optimize_test.py | 2 +- projectq/ops/_gates.py | 12 ++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 7006f5c6a..178fdec0a 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -36,7 +36,7 @@ def no_requests(monkeypatch): (Tdag, False), (S, False), (Sdag, False), (Allocate, True), (Deallocate, True), (Measure, True), (NOT, False), (Rx(0.5), True), - (Ry(0.5), True), (Rz(0.5), False), (Rxx(0.5), True), + (Ry(0.5), True), (Rz(0.5), False), (Barrier, True), (Entangle, False)]) def test_aqt_backend_is_available(single_qubit_gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) @@ -46,6 +46,16 @@ def test_aqt_backend_is_available(single_qubit_gate, is_available): assert aqt_backend.is_available(cmd) == is_available +@pytest.mark.parametrize("two_qubit_gate, is_available", + [(Rxx(0.5), True)]) +def test_aqt_backend_is_available2(two_qubit_gate, is_available): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubits = eng.allocate_qureg(2) + aqt_backend = _aqt.AQTBackend() + cmd = Command(eng, two_qubit_gate, (qubits, )) + assert aqt_backend.is_available(cmd) == is_available + + @pytest.mark.parametrize("num_ctrl_qubits, is_available", [(0, True), (1, False), (2, False), @@ -53,11 +63,12 @@ def test_aqt_backend_is_available(single_qubit_gate, is_available): def test_aqt_backend_is_available_control_not(num_ctrl_qubits, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() qureg = eng.allocate_qureg(num_ctrl_qubits) aqt_backend = _aqt.AQTBackend() cmd = Command(eng, Rx(0.5), (qubit1, ), controls=qureg) assert aqt_backend.is_available(cmd) == is_available - cmd = Command(eng, Rxx(0.5), (qubit1, ), controls=qureg) + cmd = Command(eng, Rxx(0.5), (qubit1, qubit2), controls=qureg) assert aqt_backend.is_available(cmd) == is_available diff --git a/projectq/backends/_resource_test.py b/projectq/backends/_resource_test.py index cf7122c01..6e8340feb 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -74,7 +74,7 @@ def test_resource_counter(): CNOT | (qubit1, qubit3) Rz(0.1) | qubit1 Rz(0.3) | qubit1 - Rzz(0.5) | qubit1 + Rzz(0.5) | (qubit1, qubit3) All(Measure) | qubit1 + qubit3 diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index e68b1e06f..8a6378544 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -774,4 +774,4 @@ def test_local_optimizer_apply_commutation_false(): assert received_commands[2].gate == Rz(0.2) assert received_commands[4].gate == Ry(0.1) assert received_commands[7].gate == Ry(0.2) - assert received_commands[10].gate == Rxx(0.1) \ No newline at end of file + assert received_commands[10].gate == Rxx(0.1) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 126f02706..70682a5f8 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -283,6 +283,10 @@ def matrix(self): class Rxx(BasicRotationGate): """ RotationXX gate class """ + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, -1j*cmath.sin(.5 * self.angle)], @@ -294,6 +298,10 @@ def matrix(self): class Ryy(BasicRotationGate): """ RotationYY gate class """ + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + @property def matrix(self): return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, 1j*cmath.sin(.5 * self.angle)], @@ -305,6 +313,10 @@ def matrix(self): class Rzz(BasicRotationGate): """ RotationZZ gate class """ + def __init__(self, angle): + BasicRotationGate.__init__(self, angle) + self.interchangeable_qubit_indices = [[0, 1]] + @property def matrix(self): return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0, 0, 0], From c293f64378b7bf285db5cd8abce046955a710039 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 20:04:12 +0200 Subject: [PATCH 14/16] Add missing commutaton relations between Ph and some gates --- projectq/ops/_gates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 70682a5f8..b860d808f 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -503,3 +503,6 @@ def set_commutation_relations(commuting_gates): set_commutation_relations([XGate, SqrtXGate, Rx, Rxx, Ph]) set_commutation_relations([YGate, Ry, Ryy, Ph]) set_commutation_relations([ZGate, SGate, TGate, Rz, Rzz, Ph, R]) + +for klass in [HGate, EntangleGate, SwapGate]: + set_commutation_relations([klass, Ph]) From b8b6057a991a40ec9ad0a5d8c78f0ac90d13bcf9 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 8 Jun 2021 20:06:38 +0200 Subject: [PATCH 15/16] Fix some more errors --- projectq/backends/_aqt/_aqt_test.py | 5 +++-- projectq/cengines/_optimize_test.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 178fdec0a..6d3f914c4 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -50,9 +50,10 @@ def test_aqt_backend_is_available(single_qubit_gate, is_available): [(Rxx(0.5), True)]) def test_aqt_backend_is_available2(two_qubit_gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) - qubits = eng.allocate_qureg(2) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() aqt_backend = _aqt.AQTBackend() - cmd = Command(eng, two_qubit_gate, (qubits, )) + cmd = Command(eng, two_qubit_gate, (qubit1, qubit2)) assert aqt_backend.is_available(cmd) == is_available diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 8a6378544..e917bd41b 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -414,7 +414,7 @@ def test_local_optimizer_commutable_circuit_U_example_4(U): assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 -@pytest.mark.parametrize("U", [Ph, Rz, R]) +@pytest.mark.parametrize("U", [Rz, R]) def test_local_optimizer_commutable_circuit_U_example_5(U): """Us shouldn't merge because CNOT is the wrong orientation.""" local_optimizer = _optimize.LocalOptimizer(m=10) @@ -465,7 +465,7 @@ def test_local_optimizer_commutable_circuit_U_example_6(U): assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 -@pytest.mark.parametrize("U", [Ph, Rz, R]) +@pytest.mark.parametrize("U", [Rz, R]) def test_local_optimizer_commutable_circuit_U_example_7(U): """Us shouldn't merge. Second H on wrong qubit.""" local_optimizer = _optimize.LocalOptimizer(m=10) From 0cdd9ffc855620143808102b604f43da85bca3e7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Oct 2022 21:06:23 +0000 Subject: [PATCH 16/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- projectq/cengines/_optimize.py | 176 +++++++++--------- projectq/cengines/_optimize_test.py | 168 +++++++++-------- .../cengines/_replacer/_decomposition_rule.py | 2 +- projectq/ops/_basics.py | 13 +- projectq/ops/_basics_test.py | 18 +- projectq/ops/_command.py | 8 +- projectq/ops/_command_test.py | 51 +++-- projectq/ops/_gates.py | 58 ++++-- projectq/ops/_gates_test.py | 26 +-- projectq/ops/_metagates.py | 5 +- projectq/ops/_relative_command.py | 53 +++--- projectq/ops/_relative_command_test.py | 4 +- projectq/setups/restrictedgateset.py | 2 +- 13 files changed, 317 insertions(+), 267 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 4790e7ad0..90ab9b948 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -17,9 +17,10 @@ import warnings from projectq.cengines import BasicEngine -from projectq.ops import FlushGate, FastForwardingGate, NotMergeable, XGate +from projectq.ops import FastForwardingGate, FlushGate, NotMergeable, XGate from projectq.ops._basics import Commutability + class LocalOptimizer(BasicEngine): """ LocalOptimizer is a compiler engine which optimizes locally (e.g. merging @@ -31,7 +32,8 @@ class LocalOptimizer(BasicEngine): available). For examples, see BasicRotationGate. Once a list corresponding to a qubit contains >=m gates, the pipeline is sent on to the next engine. """ - def __init__(self, cache_size=5, m=5, apply_commutation=True): # pylint: disable=invalid-name + + def __init__(self, cache_size=5, m=5, apply_commutation=True): # pylint: disable=invalid-name """ Initialize a LocalOptimizer object. @@ -127,18 +129,16 @@ def _delete_command(self, idx, command_idx): """ # List of the indices of the qubits that are involved # in command - qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits for qb in sublist] # List of the command indices corresponding to the position # of this command on each qubit id commandidcs = self._get_gate_indices(idx, command_idx, qubitids) for j in range(len(qubitids)): try: - new_list = (self._l[qubitids[j]][0:commandidcs[j]] + - self._l[qubitids[j]][commandidcs[j]+1:]) + new_list = self._l[qubitids[j]][0 : commandidcs[j]] + self._l[qubitids[j]][commandidcs[j] + 1 :] except IndexError: # If there are no more commands after that being deleted. - new_list = (self._l[qubitids[j]][0:commandidcs[j]]) + new_list = self._l[qubitids[j]][0 : commandidcs[j]] self._l[qubitids[j]] = new_list def _replace_command(self, idx, command_idx, new_command): @@ -157,19 +157,20 @@ def _replace_command(self, idx, command_idx, new_command): assert new_command.all_qubits == self._l[idx][command_idx].all_qubits # List of the indices of the qubits that are involved # in command - qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][command_idx].all_qubits for qb in sublist] # List of the command indices corresponding to the position # of this command on each qubit id commandidcs = self._get_gate_indices(idx, command_idx, qubitids) for j in range(len(qubitids)): try: - new_list = (self._l[qubitids[j]][0:commandidcs[j]] - + [new_command] - + self._l[qubitids[j]][commandidcs[j]+1:]) + new_list = ( + self._l[qubitids[j]][0 : commandidcs[j]] + + [new_command] + + self._l[qubitids[j]][commandidcs[j] + 1 :] + ) except IndexError: # If there are no more commands after that being replaced. - new_list = (self._l[qubitids[j]][0:commandidcs[j]] + [new_command]) + new_list = self._l[qubitids[j]][0 : commandidcs[j]] + [new_command] self._l[qubitids[j]] = new_list def _can_cancel_by_commutation(self, idx, qubitids, commandidcs, inverse_command, apply_commutation): @@ -191,18 +192,18 @@ def _can_cancel_by_commutation(self, idx, qubitids, commandidcs, inverse_command # We dont want to examine qubit idx because the optimizer # has already checked that the gates between the current # and mergeable gates are commutable (or a commutable list). - commandidcs.pop(qubitids.index(idx)) # Remove corresponding + commandidcs.pop(qubitids.index(idx)) # Remove corresponding # position of command for qubit idx from commandidcs - qubitids.remove(idx) # Remove qubitid representing the current + qubitids.remove(idx) # Remove qubitid representing the current # qubit in optimizer - x=1 + x = 1 for j in range(len(qubitids)): # Check that any gates between current gate and inverse # gate are all commutable this_command = self._l[qubitids[j]][commandidcs[j]] - future_command = self._l[qubitids[j]][commandidcs[j]+x] - while (future_command!=inverse_command): - if apply_commutation==False: + future_command = self._l[qubitids[j]][commandidcs[j] + x] + while future_command != inverse_command: + if apply_commutation == False: # If apply_commutation turned off, you should # only get erase=True if commands are next to # eachother on all qubits. i.e. if future_command @@ -211,21 +212,21 @@ def _can_cancel_by_commutation(self, idx, qubitids, commandidcs, inverse_command # optimizer to look at whether the separating gates # are commutable. return False - if (this_command.is_commutable(future_command)==1): - x+=1 - future_command = self._l[qubitids[j]][commandidcs[j]+x] + if this_command.is_commutable(future_command) == 1: + x += 1 + future_command = self._l[qubitids[j]][commandidcs[j] + x] erase = True else: erase = False break - if (this_command.is_commutable(future_command)==2): + if this_command.is_commutable(future_command) == 2: new_x = self._check_for_commutable_circuit(this_command, future_command, qubitids[j], commandidcs[j], 0) - if(new_x>x): - x=new_x - future_command = self._l[qubitids[j]][commandidcs[j]+x] - erase=True + if new_x > x: + x = new_x + future_command = self._l[qubitids[j]][commandidcs[j] + x] + erase = True else: - erase=False + erase = False break return erase @@ -247,31 +248,31 @@ def _can_merge_by_commutation(self, idx, qubitids, commandidcs, merged_command, # We dont want to examine qubit idx because the optimizer has already # checked that the gates between the current and mergeable gates are # commutable (or a commutable list). - commandidcs.pop(qubitids.index(idx)) # Remove corresponding position of command for qubit idx from commandidcs - qubitids.remove(idx) # Remove qubitid representing the current qubit in optimizer + commandidcs.pop(qubitids.index(idx)) # Remove corresponding position of command for qubit idx from commandidcs + qubitids.remove(idx) # Remove qubitid representing the current qubit in optimizer for j in range(len(qubitids)): # Check that any gates between current gate and mergeable # gate are commutable this_command = self._l[qubitids[j]][commandidcs[j]] possible_command = None merge = True - x=1 - while (possible_command!=merged_command): + x = 1 + while possible_command != merged_command: if not apply_commutation: # If apply_commutation turned off, you should # only get erase=True if commands are next to # eachother on all qubits. return False - future_command = self._l[qubitids[j]][commandidcs[j]+x] + future_command = self._l[qubitids[j]][commandidcs[j] + x] try: possible_command = this_command.get_merged(future_command) except: pass - if (possible_command==merged_command): + if possible_command == merged_command: merge = True break - if (this_command.is_commutable(future_command)==1): - x+=1 + if this_command.is_commutable(future_command) == 1: + x += 1 merge = True continue else: @@ -296,9 +297,11 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): x (int): If there is a commutable circuit the function returns the length x. Otherwise, returns 0. - """ + """ # commutable_circuit_list is a temp variable just used to create relative_commutable_circuits - commutable_circuit_list = command_i.gate.get_commutable_circuit_list(n=len(command_i._control_qubits), ) + commutable_circuit_list = command_i.gate.get_commutable_circuit_list( + n=len(command_i._control_qubits), + ) relative_commutable_circuits = [] # Keep a list of circuits that start with # next_command. @@ -307,31 +310,31 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): relative_commutable_circuits.append(relative_circuit) # Create dictionaries { absolute_qubit_idx : relative_qubit_idx } # For the purposes of fast lookup, also { relative_qubit_idx : absolute_qubit_idx } - abs_to_rel = { idx : 0 } - rel_to_abs = { 0 : idx } + abs_to_rel = {idx: 0} + rel_to_abs = {0: idx} # If the current command is a CNOT, we set the target qubit idx # to 0 if isinstance(command_i.gate, XGate): - if len(command_i._control_qubits)==1: + if len(command_i._control_qubits) == 1: # At this point we know we have a CNOT # we reset the dictionaries so that the # target qubit in the abs dictionary # corresponds to the target qubit in the # rel dictionary - abs_to_rel = {command_i.qubits[0][0].id : 0} - rel_to_abs = {0 : command_i.qubits[0][0].id} - y=0 - absolute_circuit = self._l[idx][i+x+1:] + abs_to_rel = {command_i.qubits[0][0].id: 0} + rel_to_abs = {0: command_i.qubits[0][0].id} + y = 0 + absolute_circuit = self._l[idx][i + x + 1 :] # If no (more) relative commutable circuits to check against, # break out of this while loop and move on to next command_i. while relative_commutable_circuits: # If all the viable relative_circuits have been deleted # you want to just move on relative_circuit = relative_commutable_circuits[0] - while (y(len(absolute_circuit)-1)): + if y > (len(absolute_circuit) - 1): # The absolute circuit is too short to match the relative_circuit # i.e. if the absolute circuit is of len=3, you can't have absolute_circuit[3] # only absolute_circuit[0] - absolute_circuit[2] @@ -351,16 +354,16 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): # remember next_command = absolute_circuit[y]. for qubit in next_command.qubits: # We know a and r should correspond in both dictionaries. - a=qubit[0].id - r=relative_circuit[y].relative_qubit_idcs[0] + a = qubit[0].id + r = relative_circuit[y].relative_qubit_idcs[0] if a in abs_to_rel.keys(): # If a in abs_to_rel, r will be in rel_to_abs - if (abs_to_rel[a] != r): + if abs_to_rel[a] != r: if relative_commutable_circuits: relative_commutable_circuits.pop(0) break if r in rel_to_abs.keys(): - if (rel_to_abs[r] != a): + if rel_to_abs[r] != a: if relative_commutable_circuits: relative_commutable_circuits.pop(0) break @@ -371,16 +374,16 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): # HERE: we know the qubit idcs don't contradict our dictionaries. for ctrl_qubit in next_command.control_qubits: # We know a and r should correspond in both dictionaries. - a=ctrl_qubit.id - r=relative_circuit[y].relative_ctrl_idcs[0] + a = ctrl_qubit.id + r = relative_circuit[y].relative_ctrl_idcs[0] if a in abs_to_rel.keys(): # If a in abs_to_rel, r will be in rel_to_abs - if (abs_to_rel[a] != r): + if abs_to_rel[a] != r: if relative_commutable_circuits: relative_commutable_circuits.pop(0) break if r in rel_to_abs.keys(): - if (rel_to_abs[r] != a): + if rel_to_abs[r] != a: if relative_commutable_circuits: relative_commutable_circuits.pop(0) break @@ -390,16 +393,16 @@ def _check_for_commutable_circuit(self, command_i, next_command, idx, i, x): break # HERE: we know all relative/absolute qubits/ctrl qubits do not # contradict dictionaries and are assigned. - y+=1 - if (y==len(relative_circuit)): - # Up to the yth term in relative_circuit, we have checked - # that absolute_circuit[y] == relative_circuit[y] - # This means absolute_circuit is commutable - # with command_i + y += 1 + if y == len(relative_circuit): + # Up to the yth term in relative_circuit, we have checked + # that absolute_circuit[y] == relative_circuit[y] + # This means absolute_circuit is commutable + # with command_i # Set x = x+len(relative_circuit)-1 and continue through # while loop as though the list was a commutable gate - x+=(len(relative_circuit)) - relative_commutable_circuits=[] + x += len(relative_circuit) + relative_commutable_circuits = [] return x return x @@ -427,41 +430,40 @@ def _optimize(self, idx, lim=None): continue x = 0 - while (i+x+1 < limit): + while i + x + 1 < limit: # At this point: # Gate i is commutable with each gate up to i+x, so # check if i and i+x+1 can be cancelled or merged inv = self._l[idx][i].get_inverse() - if inv == self._l[idx][i+x+1]: + if inv == self._l[idx][i + x + 1]: # List of the indices of the qubits that are involved # in command - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] # List of the command indices corresponding to the position # of this command on each qubit id commandidcs = self._get_gate_indices(idx, i, qubitids) erase = self._can_cancel_by_commutation(idx, qubitids, commandidcs, inv, self._apply_commutation) if erase: - # Delete the inverse commands. Delete the later - # one first so the first index doesn't - # change before you delete it. - self._delete_command(idx, i+x+1) + # Delete the inverse commands. Delete the later + # one first so the first index doesn't + # change before you delete it. + self._delete_command(idx, i + x + 1) self._delete_command(idx, i) i = 0 limit -= 2 break try: - merged_command = self._l[idx][i].get_merged(self._l[idx][i+x+1]) + merged_command = self._l[idx][i].get_merged(self._l[idx][i + x + 1]) # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] commandidcs = self._get_gate_indices(idx, i, qubitids) - merge = self._can_merge_by_commutation(idx, qubitids, commandidcs, - merged_command, self._apply_commutation) + merge = self._can_merge_by_commutation( + idx, qubitids, commandidcs, merged_command, self._apply_commutation + ) if merge: # Delete command i+x+1 first because i+x+1 # will not affect index of i - self._delete_command(idx, i+x+1) + self._delete_command(idx, i + x + 1) self._replace_command(idx, i, merged_command) i = 0 limit -= 1 @@ -475,24 +477,24 @@ def _optimize(self, idx, lim=None): if not self._apply_commutation: break command_i = self._l[idx][i] - next_command = self._l[idx][i+x+1] - #----------------------------------------------------------# + next_command = self._l[idx][i + x + 1] + # ----------------------------------------------------------# # See if next_command is commutable with this_command. # # - #----------------------------------------------------------# + # ----------------------------------------------------------# commutability_check = command_i.is_commutable(next_command) - if(commutability_check == Commutability.COMMUTABLE): - x=x+1 + if commutability_check == Commutability.COMMUTABLE: + x = x + 1 continue - #----------------------------------------------------------# + # ----------------------------------------------------------# # See if next_command is part of a circuit which is # # commutable with this_command. # - #----------------------------------------------------------# + # ----------------------------------------------------------# new_x = 0 - if(commutability_check == Commutability.MAYBE_COMMUTABLE): + if commutability_check == Commutability.MAYBE_COMMUTABLE: new_x = self._check_for_commutable_circuit(command_i, next_command, idx, i, x) - if(new_x>x): - x=new_x + if new_x > x: + x = new_x continue else: break diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 223584c6b..c12ccaabc 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -14,16 +14,7 @@ """Tests for projectq.cengines._optimize.py.""" import math -import pytest -import math -from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import (CNOT, H, Rx, Ry, Rz, Rxx, Ryy, Rzz, Measure, AllocateQubitGate, X, - FastForwardingGate, ClassicalInstructionGate, XGate, Ph, X, - SqrtX, Y, Z, S, T, R) -from projectq.setups import restrictedgateset, trapped_ion_decomposer -from projectq.cengines import _optimize import pytest from projectq import MainEngine @@ -34,10 +25,24 @@ ClassicalInstructionGate, FastForwardingGate, H, + Measure, + Ph, + R, Rx, + Rxx, Ry, + Ryy, + Rz, + Rzz, + S, + SqrtX, + T, X, + XGate, + Y, + Z, ) +from projectq.setups import restrictedgateset, trapped_ion_decomposer def test_local_optimizer_init_api_change(): @@ -140,11 +145,12 @@ def test_local_optimizer_cancel_inverse(): assert received_commands[1].qubits[0][0].id == qb1[0].id assert received_commands[1].control_qubits[0].id == qb0[0].id + def test_local_optimizer_cancel_separated_inverse(): - """ Tests the situation where the next command on + """Tests the situation where the next command on this qubit is an inverse command, but another qubit involved is separated from the inverse by only commutable - gates. The two commands should cancel. """ + gates. The two commands should cancel.""" local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -161,13 +167,13 @@ def test_local_optimizer_cancel_separated_inverse(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 1 assert received_commands[0].gate == Rx(0.3) assert received_commands[0].qubits[0][0].id == qb1[0].id + def test_local_optimizer_mergeable_gates(): local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) @@ -192,8 +198,7 @@ def test_local_optimizer_mergeable_gates(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) # Expect one gate each of Rx, Ry, Rz, Rxx, Ryy, Rzz assert len(received_commands) == 6 @@ -204,6 +209,7 @@ def test_local_optimizer_mergeable_gates(): assert received_commands[4].gate == Ryy(1.0) assert received_commands[5].gate == Rzz(1.0) + def test_local_optimizer_separated_mergeable_gates(): """Tests the situation where the next command on this qubit is a mergeable command, but another qubit involved is separated @@ -215,8 +221,8 @@ def test_local_optimizer_separated_mergeable_gates(): eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - #assert len(backend.received_commands) == 0 - #Reminder: Rxx and Rx commute + # assert len(backend.received_commands) == 0 + # Reminder: Rxx and Rx commute Rxx(0.3) | (qb0, qb1) Rx(math.pi) | qb1 Rxx(0.8) | (qb0, qb1) @@ -231,12 +237,11 @@ def test_local_optimizer_separated_mergeable_gates(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[0].gate == Rxx(2.3) assert received_commands[1].gate == H - assert received_commands[2].gate == Rx(math.pi+0.3) + assert received_commands[2].gate == Rx(math.pi + 0.3) assert received_commands[3].gate == Ry(0.5) assert received_commands[0].qubits[0][0].id == qb0[0].id assert received_commands[0].qubits[1][0].id == qb1[0].id @@ -244,6 +249,7 @@ def test_local_optimizer_separated_mergeable_gates(): assert received_commands[2].qubits[0][0].id == qb1[0].id assert received_commands[3].qubits[0][0].id == qb1[0].id + def test_local_optimizer_identity_gates(): local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) @@ -262,10 +268,11 @@ def test_local_optimizer_identity_gates(): assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(0.5) + @pytest.mark.parametrize(["U", "Ru", "Ruu"], [[X, Rx, Rxx], [Y, Ry, Ryy], [Z, Rz, Rzz]]) def test_local_optimizer_commutable_gates_parameterized_1(U, Ru, Ruu): - """ Iterate through gates of the X, Y, Z type and - check that they correctly commute with eachother and with Ph. + """Iterate through gates of the X, Y, Z type and + check that they correctly commute with eachother and with Ph. """ local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) @@ -296,15 +303,15 @@ def test_local_optimizer_commutable_gates_parameterized_1(U, Ru, Ruu): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 4 + @pytest.mark.parametrize("U", [Ph, Rz, R]) @pytest.mark.parametrize("C", [Z, S, T]) def test_local_optimizer_commutable_gates_parameterized_2(U, C): - """ Tests that the Rzz, Ph, Rz, R gates commute through S, T, Z.""" + """Tests that the Rzz, Ph, Rz, R gates commute through S, T, Z.""" local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -319,14 +326,14 @@ def test_local_optimizer_commutable_gates_parameterized_2(U, C): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 3 - #assert received_commands[0].gate == Ph(0.8) + # assert received_commands[0].gate == Ph(0.8) + def test_local_optimizer_commutable_gates_SqrtX(): - """ Tests that the X, Rx, Rxx, Ph gates commute through SqrtX.""" + """Tests that the X, Rx, Rxx, Ph gates commute through SqrtX.""" local_optimizer = _optimize.LocalOptimizer(m=5) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -346,14 +353,14 @@ def test_local_optimizer_commutable_gates_SqrtX(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_U_example_1(U): - """ Example circuit where the Rzs should merge. """ + """Example circuit where the Rzs should merge.""" # Rzs should merge local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) @@ -369,16 +376,16 @@ def test_local_optimizer_commutable_circuit_U_example_1(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[0].gate == U(0.3) assert len(received_commands) == 4 + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_U_example_2(U): - """ Us shouldn't merge (Although in theory they should, - this would require a new update.) """ + """Us shouldn't merge (Although in theory they should, + this would require a new update.)""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -393,15 +400,15 @@ def test_local_optimizer_commutable_circuit_U_example_2(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[1].gate == U(0.1) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_U_example_3(U): - """ Us should not merge because they are operating on different qubits. """ + """Us should not merge because they are operating on different qubits.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -416,12 +423,12 @@ def test_local_optimizer_commutable_circuit_U_example_3(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[1].gate == U(0.1) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_U_example_4(U): """Us shouldn't merge because they are operating on different qubits.""" @@ -439,12 +446,12 @@ def test_local_optimizer_commutable_circuit_U_example_4(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Rz, R]) def test_local_optimizer_commutable_circuit_U_example_5(U): """Us shouldn't merge because CNOT is the wrong orientation.""" @@ -462,12 +469,12 @@ def test_local_optimizer_commutable_circuit_U_example_5(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Rz, R]) def test_local_optimizer_commutable_circuit_U_example_6(U): """Us shouldn't merge because the circuit is in the wrong @@ -489,13 +496,13 @@ def test_local_optimizer_commutable_circuit_U_example_6(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) print(cmd) assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 + @pytest.mark.parametrize("U", [Rz, R]) def test_local_optimizer_commutable_circuit_U_example_7(U): """Us shouldn't merge. Second H on wrong qubit.""" @@ -513,12 +520,12 @@ def test_local_optimizer_commutable_circuit_U_example_7(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert received_commands[0].gate == U(0.1) assert len(received_commands) == 5 + def test_local_optimizer_commutable_circuit_CNOT_example_1(): """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) @@ -536,12 +543,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_1(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 3 assert received_commands[0].gate == H + def test_local_optimizer_commutable_circuit_CNOT_example_2(): """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) @@ -559,12 +566,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_2(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 3 assert received_commands[0].gate == H + def test_local_optimizer_commutable_circuit_CNOT_example_3(): """This example should commute.""" local_optimizer = _optimize.LocalOptimizer(m=10) @@ -582,12 +589,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_3(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 3 assert received_commands[0].gate == H + def test_local_optimizer_commutable_circuit_CNOT_example_4(): """This example shouldn't commute because the CNOT is the wrong orientation.""" @@ -606,12 +613,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_4(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate + def test_local_optimizer_commutable_circuit_CNOT_example_5(): """This example shouldn't commute because the CNOT is the wrong orientation.""" @@ -630,12 +637,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_5(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate + def test_local_optimizer_commutable_circuit_CNOT_example_6(): """This example shouldn't commute because the CNOT is the wrong orientation. Same as example_3 with middle CNOT reversed.""" @@ -654,12 +661,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_6(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate + def test_local_optimizer_commutable_circuit_CNOT_example_7(): """This example shouldn't commute because the CNOT is the wrong orientation. Same as example_1 with middle CNOT reversed.""" @@ -678,12 +685,12 @@ def test_local_optimizer_commutable_circuit_CNOT_example_7(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate + def test_local_optimizer_commutable_circuit_CNOT_example_8(): """This example shouldn't commute because the CNOT is the wrong orientation. Same as example_2 with middle CNOT reversed.""" @@ -702,17 +709,17 @@ def test_local_optimizer_commutable_circuit_CNOT_example_8(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 5 assert received_commands[0].gate.__class__ == XGate + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_CNOT_and_U_example_1(U): """This example is to check everything works as expected when the commutable circuit is on later commands in the optimizer - dictionary. The number of commmands should reduce from 10 to 7. """ + dictionary. The number of commmands should reduce from 10 to 7.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -733,16 +740,16 @@ def test_local_optimizer_commutable_circuit_CNOT_and_U_example_1(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 7 assert received_commands[6].gate == H + @pytest.mark.parametrize("U", [Ph, Rz, R]) def test_local_optimizer_commutable_circuit_CNOT_and_U_example_2(U): - """ This example is to check everything works as expected when - the commutable circuit is on qubits 3, 4, 5. """ + """This example is to check everything works as expected when + the commutable circuit is on qubits 3, 4, 5.""" local_optimizer = _optimize.LocalOptimizer(m=10) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) @@ -765,40 +772,39 @@ def test_local_optimizer_commutable_circuit_CNOT_and_U_example_2(U): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 7 assert received_commands[6].gate == H + def test_local_optimizer_apply_commutation_false(): """Test that the local_optimizer behaves as if commutation isn't an option - if you set apply_commutation = False. """ + if you set apply_commutation = False.""" local_optimizer = _optimize.LocalOptimizer(m=10, apply_commutation=False) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() - Rz(0.1) | qb0 # Rzs next to eachother should merge + Rz(0.1) | qb0 # Rzs next to eachother should merge Rz(0.4) | qb0 - Rzz(0.3) | (qb0, qb1) # Rzs either side of Rzz should not merge + Rzz(0.3) | (qb0, qb1) # Rzs either side of Rzz should not merge Rz(0.2) | qb0 - H | qb0 # Hs next to eachother should cancel + H | qb0 # Hs next to eachother should cancel H | qb0 - Ry(0.1) | qb1 # Ry should not merge with the Rz on the other side of - H | qb0 # a commutable list + Ry(0.1) | qb1 # Ry should not merge with the Rz on the other side of + H | qb0 # a commutable list CNOT | (qb0, qb1) H | qb0 Ry(0.2) | qb1 Rxx(0.2) | (qb0, qb1) - Rx(0.1) | qb1 # Rxxs either side of Rx shouldn't merge + Rx(0.1) | qb1 # Rxxs either side of Rx shouldn't merge Rxx(0.1) | (qb0, qb1) eng.flush() received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 11 assert received_commands[0].gate == Rz(0.5) diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index c4c253c32..c80a5d9ce 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -54,7 +54,7 @@ def __init__(self, gate_class, gate_decomposer, gate_recognizer=lambda cmd: True "\nDid you pass in someGate instead of someGate.__class__?" ) if gate_class == type.__class__: - "\nDid you pass in someGate instead of someGate.__class__?" + "\nDid you pass in someGate instead of someGate.__class__?" if gate_class in (type.__class__, BasicGateMeta): raise ThisIsNotAGateClassError( "gate_class is type.__class__ instead of a type of BasicGate." diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 74492f651..44b10e7d7 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -39,7 +39,7 @@ from projectq.types import BasicQubit -from ._command import Command, apply_command, Commutability +from ._command import Command, Commutability, apply_command ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10**-ANGLE_PRECISION @@ -67,14 +67,14 @@ class BasicGateMeta(type): """ Meta class for all gates; mainly used to ensure that all gate classes have some basic class variable defined. """ - def __new__(cls, name, bases, attrs): + def __new__(cls, name, bases, attrs): def get_commutable_gates(self): return self._commutable_gates - return super(BasicGateMeta, cls).__new__(cls, name, bases, {**attrs, - get_commutable_gates.__name__: get_commutable_gates, - '_commutable_gates': set()}) + return super().__new__( + cls, name, bases, {**attrs, get_commutable_gates.__name__: get_commutable_gates, '_commutable_gates': set()} + ) class BasicGate(metaclass=BasicGateMeta): @@ -85,6 +85,7 @@ class BasicGate(metaclass=BasicGateMeta): """ Base class of all gates. (Don't use it directly but derive from it) """ + def __init__(self): """ Initialize a basic gate. @@ -279,7 +280,7 @@ def is_commutable(self, other): commutability (Commutability) : An enum which indicates whether the next gate is commutable, not commutable or maybe commutable. - """ + """ for gate in self.get_commutable_gates(): if type(other) is gate: return Commutability.COMMUTABLE diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 7d9be5751..cf25cbf3f 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -15,23 +15,13 @@ import math import sys -import numpy as np -import pytest - -from projectq.types import Qubit, Qureg -from projectq.ops import Command, X, NOT -from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import _basics -from projectq.ops import _gates -from projectq.ops import _metagates import numpy as np import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import Command, X, _basics +from projectq.ops import NOT, Command, X, _basics, _gates, _metagates from projectq.types import Qubit, Qureg, WeakQubitRef @@ -363,9 +353,9 @@ def test_matrix_gate(): def test_is_commutable(): - """ At the gate level is_commutable can return + """At the gate level is_commutable can return true or false. Test that is_commutable is working - as expected. """ + as expected.""" gate1 = _basics.BasicRotationGate(math.pi) gate2 = _basics.MatrixGate() gate3 = _basics.BasicRotationGate(math.pi) @@ -375,4 +365,4 @@ def test_is_commutable(): gate5 = _gates.H gate6 = _metagates.C(NOT) assert not gate4.is_commutable(gate5) - assert not gate4.is_commutable(gate6) \ No newline at end of file + assert not gate4.is_commutable(gate6) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 00cd986d1..159864c25 100644 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -116,7 +116,7 @@ def __init__( self.control_qubits = controls # property self.engine = engine # property self.control_state = control_state # property - self._commutable_circuit_list = self.gate.get_commutable_circuit_list(n=len(self.control_qubits)) # property + self._commutable_circuit_list = self.gate.get_commutable_circuit_list(n=len(self.control_qubits)) # property @property def qubits(self): @@ -381,6 +381,7 @@ def to_string(self, symbols=False): cstring = "C" * len(ctrlqubits) return f"{cstring + self.gate.to_string(symbols)} | {qstring}" + def overlap(tuple1, tuple2): """ Takes two tuples of lists, flattens them and counts the number @@ -396,12 +397,13 @@ def overlap(tuple1, tuple2): """ flat_tuple1 = [item for sublist in tuple1 for item in sublist] flat_tuple2 = [item for sublist in tuple2 for item in sublist] - n=0 + n = 0 for element in flat_tuple1: if element in flat_tuple2: - n+=1 + n += 1 return n + class Commutability(IntEnum): NOT_COMMUTABLE = 0 COMMUTABLE = 1 diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 14ffba4ea..4ece06a02 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -21,13 +21,24 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import Rxx, Ry, H, Rz, CNOT, X, SqrtX, Ph from projectq.meta import ComputeTag, canonical_ctrl_state -from projectq.ops import BasicGate, CtrlAll, NotMergeable, Rx -from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import _command +from projectq.ops import ( + CNOT, + BasicGate, + CtrlAll, + H, + NotMergeable, + Ph, + Rx, + Rxx, + Ry, + Rz, + SqrtX, + X, + _command, +) from projectq.ops._command import Commutability - +from projectq.types import Qubit, Qureg, WeakQubitRef @pytest.fixture @@ -149,11 +160,11 @@ def test_command_is_identity(main_engine): def test_overlap(): - """ Test the overlap function is working as + """Test the overlap function is working as expected.""" - tuple1 = ([1,2],[3]) - tuple2 = ([2],[3,0]) - tuple3 = ([0,0,0],) + tuple1 = ([1, 2], [3]) + tuple2 = ([2], [3, 0]) + tuple3 = ([0, 0, 0],) assert _command.overlap(tuple1, tuple2) == 2 assert _command.overlap(tuple1, tuple3) == 0 @@ -165,7 +176,7 @@ def test_command_is_commutable(main_engine): might have a commutable circuit CNOT's commutable circuit wont be recognised at this level because CNOT.__gate__ = ControlledGate - whereas in the optimizer CNOT.__gate__ = XGate. """ + whereas in the optimizer CNOT.__gate__ = XGate.""" qubit1 = Qureg([Qubit(main_engine, 0)]) qubit2 = Qureg([Qubit(main_engine, 1)]) cmd1 = _command.Command(main_engine, Rx(0.5), (qubit1,)) @@ -179,18 +190,20 @@ def test_command_is_commutable(main_engine): cmd9 = _command.Command(main_engine, X, (qubit1,)) cmd10 = _command.Command(main_engine, SqrtX, (qubit1,)) cmd11 = _command.Command(main_engine, Ph(math.pi), (qubit1,)) - assert not cmd1.is_commutable(cmd2) #Identical qubits, identical gate + assert not cmd1.is_commutable(cmd2) # Identical qubits, identical gate assert _command.overlap(cmd1.all_qubits, cmd3.all_qubits) == 0 - assert not cmd1.is_commutable(cmd3) #Different qubits, same gate - assert cmd3.is_commutable(cmd4) #Qubits in common, different but commutable gates - assert not cmd4.is_commutable(cmd5) #Qubits in common, different, non-commutable gates - assert cmd6.is_commutable(cmd7) == Commutability.MAYBE_COMMUTABLE.value # Rz has a commutable circuit which starts with H - assert not cmd7.is_commutable(cmd8) # H does not have a commutable circuit which starts with CNOT - assert cmd1.is_commutable(cmd9) # Rx commutes with X + assert not cmd1.is_commutable(cmd3) # Different qubits, same gate + assert cmd3.is_commutable(cmd4) # Qubits in common, different but commutable gates + assert not cmd4.is_commutable(cmd5) # Qubits in common, different, non-commutable gates + assert ( + cmd6.is_commutable(cmd7) == Commutability.MAYBE_COMMUTABLE.value + ) # Rz has a commutable circuit which starts with H + assert not cmd7.is_commutable(cmd8) # H does not have a commutable circuit which starts with CNOT + assert cmd1.is_commutable(cmd9) # Rx commutes with X assert cmd9.is_commutable(cmd1) - assert cmd10.is_commutable(cmd9) # SqrtX commutes with X + assert cmd10.is_commutable(cmd9) # SqrtX commutes with X assert cmd9.is_commutable(cmd10) - assert cmd11.is_commutable(cmd9) # Ph commutes with X + assert cmd11.is_commutable(cmd9) # Ph commutes with X assert cmd9.is_commutable(cmd11) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index f9fefe823..a61d6e765 100644 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -47,7 +47,6 @@ import numpy as np -from ._metagates import C from ._basics import ( BasicGate, BasicPhaseGate, @@ -57,7 +56,7 @@ SelfInverseGate, ) from ._command import apply_command -from ._metagates import get_inverse +from ._metagates import C, get_inverse from ._relative_command import RelativeCommand @@ -91,18 +90,25 @@ def matrix(self): return np.matrix([[0, 1], [1, 0]]) def get_commutable_circuit_list(self, n=0): - """ Sets _commutable_circuit_list for C(NOT, n) where + """Sets _commutable_circuit_list for C(NOT, n) where n is the number of controls Args: - n (int): The number of controls on this gate. """ - if (n == 1): + n (int): The number of controls on this gate.""" + if n == 1: # i.e. this is a CNOT gate (one control) # We define the qubit with the NOT as qb0, the qubit with # the control as qb1, then all next qbs are labelled 2,3 etc. - return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(2,), relative_ctrl_idcs=(0,)), RelativeCommand(H,(0,))]] + return [ + [ + RelativeCommand(H, (0,)), + RelativeCommand(C(NOT), (2,), relative_ctrl_idcs=(0,)), + RelativeCommand(H, (0,)), + ] + ] else: - return [] # don't change _commutable_circuit_list + return [] # don't change _commutable_circuit_list + #: Shortcut (instance of) :class:`projectq.ops.XGate` X = NOT = XGate() @@ -160,6 +166,7 @@ def __str__(self): #: Inverse (and shortcut) of :class:`projectq.ops.SGate` Sdag = Sdagger = get_inverse(S) + class TGate(BasicGate): """T gate class.""" @@ -310,16 +317,22 @@ def matrix(self): class Rz(BasicRotationGate): - """ RotationZ gate class.""" + """RotationZ gate class.""" def get_commutable_circuit_list(self, n=0): - """ Sets _commutable_circuit_list for C(NOT, n) where + """Sets _commutable_circuit_list for C(NOT, n) where n is the number of controls Args: - n (int): The number of controls on this gate. """ + n (int): The number of controls on this gate.""" - return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + return [ + [ + RelativeCommand(H, (0,)), + RelativeCommand(C(NOT), (0,), relative_ctrl_idcs=(1,)), + RelativeCommand(H, (0,)), + ], + ] @property def matrix(self): @@ -333,7 +346,7 @@ def matrix(self): class Rxx(BasicRotationGate): - """ RotationXX gate class.""" + """RotationXX gate class.""" def __init__(self, angle): BasicRotationGate.__init__(self, angle) @@ -353,7 +366,7 @@ def matrix(self): class Ryy(BasicRotationGate): - """ RotationYY gate class.""" + """RotationYY gate class.""" def __init__(self, angle): BasicRotationGate.__init__(self, angle) @@ -373,7 +386,7 @@ def matrix(self): class Rzz(BasicRotationGate): - """ RotationZZ gate class.""" + """RotationZZ gate class.""" def __init__(self, angle): BasicRotationGate.__init__(self, angle) @@ -393,15 +406,21 @@ def matrix(self): class R(BasicPhaseGate): - """ Phase-shift gate (equivalent to Rz up to a global phase) """ + """Phase-shift gate (equivalent to Rz up to a global phase)""" def get_commutable_circuit_list(self, n=0): - """ Sets _commutable_circuit_list for C(NOT, n) where + """Sets _commutable_circuit_list for C(NOT, n) where n is the number of controls Args: - n (int): The number of controls on this gate. """ - return [[RelativeCommand(H,(0,)), RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), RelativeCommand(H,(0,))],] + n (int): The number of controls on this gate.""" + return [ + [ + RelativeCommand(H, (0,)), + RelativeCommand(C(NOT), (0,), relative_ctrl_idcs=(1,)), + RelativeCommand(H, (0,)), + ], + ] @property def matrix(self): @@ -582,13 +601,16 @@ def __hash__(self): """Compute the hash of the object.""" return hash(str(self)) + # Define commutation relations + def set_commutation_relations(commuting_gates): for klass in commuting_gates: klass._commutable_gates.update(commuting_gates) klass._commutable_gates.discard(klass) + set_commutation_relations([XGate, SqrtXGate, Rx, Rxx, Ph]) set_commutation_relations([YGate, Ry, Ryy, Ph]) set_commutation_relations([ZGate, SGate, TGate, Rz, Rzz, Ph, R]) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 78e6d1d68..7e9a4769f 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -20,12 +20,18 @@ import pytest from projectq import MainEngine -from projectq.ops import All, FlipBits, Measure, _gates, get_inverse - - -from projectq.ops import _gates -from projectq.ops import CNOT, H, C, NOT -from projectq.ops import RelativeCommand +from projectq.ops import ( + CNOT, + NOT, + All, + C, + FlipBits, + H, + Measure, + RelativeCommand, + _gates, + get_inverse, +) def test_h_gate(): @@ -33,9 +39,7 @@ def test_h_gate(): assert gate == gate.get_inverse() assert str(gate) == "H" assert np.array_equal(gate.matrix, 1.0 / math.sqrt(2) * np.matrix([[1, 1], [1, -1]])) - assert np.array_equal( - gate.matrix, 1.0 / math.sqrt(2) * np.matrix([[1, 1], [1, -1]]) - ) + assert np.array_equal(gate.matrix, 1.0 / math.sqrt(2) * np.matrix([[1, 1], [1, -1]])) assert isinstance(_gates.H, _gates.HGate) @@ -196,9 +200,7 @@ def test_ry_commutation(angle1, angle2): @pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rz(angle): gate = _gates.Rz(angle) - expected_matrix = np.matrix( - [[cmath.exp(-0.5 * 1j * angle), 0], [0, cmath.exp(0.5 * 1j * angle)]] - ) + expected_matrix = np.matrix([[cmath.exp(-0.5 * 1j * angle), 0], [0, cmath.exp(0.5 * 1j * angle)]]) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 1664b54c8..83c6a0ed4 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -34,6 +34,7 @@ class ControlQubitError(Exception): """Exception thrown when wrong number of control qubits are supplied.""" + class DaggeredGate(BasicGate): """ Wrapper class allowing to execute the inverse of a gate, even when it does not define one. @@ -93,6 +94,7 @@ def __hash__(self): """Compute the hash of the object.""" return hash(str(self)) + def get_inverse(gate): """ Return the inverse of a gate. @@ -220,11 +222,11 @@ def __eq__(self, other): """Compare two ControlledGate objects (return True if equal).""" return isinstance(other, self.__class__) and self._gate == other._gate and self._n == other._n - @property def commutable_circuit_list(self): return self._gate.get_commutable_circuit_list(self._n) + def C(gate, n_qubits=1): """ Return n-controlled version of the provided gate. @@ -240,6 +242,7 @@ def C(gate, n_qubits=1): """ return ControlledGate(gate, n_qubits) + class Tensor(BasicGate): """ Wrapper class allowing to apply a (single-qubit) gate to every qubit in a quantum register. diff --git a/projectq/ops/_relative_command.py b/projectq/ops/_relative_command.py index 5a946471b..1166f3b89 100644 --- a/projectq/ops/_relative_command.py +++ b/projectq/ops/_relative_command.py @@ -15,58 +15,65 @@ This file defines the RelativeCommand class. RelativeCommand should be used to represent a Command -when there is no main_engine, for example within Gate -definitions. Using RelativeCommand we can define -commutable_circuits for a particular gate. +when there is no main_engine, for example within Gate +definitions. Using RelativeCommand we can define +commutable_circuits for a particular gate. Example: .. code-block:: python class Rz(BasicRotationGate): - def __init__(self, angle): BasicRotationGate.__init__(self, angle) - self._commutable_gates = [Rzz,] - self._commutable_circuit_list = [[RelativeCommand(H,(0,)), - RelativeCommand(C(NOT),(0,), relative_ctrl_idcs=(1,)), - RelativeCommand(H,(0,))],] + self._commutable_gates = [ + Rzz, + ] + self._commutable_circuit_list = [ + [ + RelativeCommand(H, (0,)), + RelativeCommand(C(NOT), (0,), relative_ctrl_idcs=(1,)), + RelativeCommand(H, (0,)), + ], + ] The _commutable_circuit_list has been defined using RelativeCommand objects. The is_commutable function defined in the Command class looks -for _commutable_circuits in the definition of its' gate. Then we can -check if there might be a commutable circuit coming after a Command. +for _commutable_circuits in the definition of its' gate. Then we can +check if there might be a commutable circuit coming after a Command. This is used in the _check_for_commutable_circuit function and in the LocalOptimizer in _optimize.py cmd1 = _command.Command(main_engine, Rz(0.2), (qubit1,)) cmd2 = _command.Command(main_engine, H, (qubit1,)) - if (cmd1.is_commutable(cmd2) == 2): + if (cmd1.is_commutable(cmd2) == 2): # Check for a commutable circuit -Rz has a commutable circuit which starts with H. If is_commutable returns '2' -it indicates that the next command may be the start of a commutable circuit. +Rz has a commutable circuit which starts with H. If is_commutable returns '2' +it indicates that the next command may be the start of a commutable circuit. """ from projectq.ops._metagates import ControlledGate -class RelativeCommand(object): - """ Alternative representation of a Command. + +class RelativeCommand: + """Alternative representation of a Command. Class: Used to represent commands when there is no engine. - i.e. in the definition of a relative commutable_circuit - within a gate class. + i.e. in the definition of a relative commutable_circuit + within a gate class. Attributes: gate: The gate class. _gate: The true gate class if gate is a metagate. relative_qubit_idcs: Tuple of integers, representing the relative qubit idcs in a commutable_circuit. - relative_ctrl_idcs: Tuple of integers, representing the + relative_ctrl_idcs: Tuple of integers, representing the relative control qubit idcs in a commutable_circuit. """ + def __init__(self, gate, relative_qubit_idcs, relative_ctrl_idcs=()): self.gate = gate self.relative_qubit_idcs = relative_qubit_idcs @@ -87,7 +94,7 @@ def to_string(self, symbols=False): qubits = self.relative_qubit_idcs ctrlqubits = self.relative_ctrl_idcs if len(ctrlqubits) > 0: - qubits = (self.relative_ctrl_idcs, ) + qubits + qubits = (self.relative_ctrl_idcs,) + qubits qstring = "" if len(qubits) == 1: qstring = str(qubits) @@ -100,10 +107,12 @@ def to_string(self, symbols=False): return self.gate.to_string(symbols) + " | " + qstring def equals(self, other): - if ((type(self.gate) is type(other.gate)) + if ( + (type(self.gate) is type(other.gate)) and (self.relative_qubit_idcs == other.relative_qubit_idcs) and (self.relative_ctrl_idcs == other.relative_ctrl_idcs) - and (type(self._gate) is type(self._gate))): + and (type(self._gate) is type(self._gate)) + ): return True else: - return False + return False diff --git a/projectq/ops/_relative_command_test.py b/projectq/ops/_relative_command_test.py index 7c7c5efbf..a6ed9281f 100644 --- a/projectq/ops/_relative_command_test.py +++ b/projectq/ops/_relative_command_test.py @@ -1,5 +1,5 @@ -from projectq.ops import H, CNOT -from projectq.ops import _basics, RelativeCommand +from projectq.ops import CNOT, H, RelativeCommand, _basics + def test_relative_command_equals(): cmd1 = RelativeCommand(H, 0) diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 1316648be..be58f8eac 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -46,7 +46,7 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements two_qubit_gates=(CNOT,), other_gates=(), compiler_chooser=default_chooser, - apply_commutation=True + apply_commutation=True, ): """ Return an engine list to compile to a restricted gate set.