Skip to content

Commit

Permalink
Ctrl generalize (#400)
Browse files Browse the repository at this point in the history
* Init

* Apply control through compute engine. Added Enum class

* Apply control through compute engine. Added Enum class

* Apply control through compute engine, added enum class

* Fix small bug due to wrong default value, clean up output

* abolished decomp

* Revert "abolished decomp"

This reverts commit 36743a1.

* Apply Control through decomposition. Added Test Files

* Fix a few issues with new control state

- Address inconsistency with qubit state ordering when using integers
  as input control state
- Add canonical_ctrl_state function to centralise functionality
- Fix some file encoding
- Fix tests

* Update examples/control_tester.py

* Add some missing license headers and fix some tests

* Add missing docstring

* Cleanup some code in AQT and IBM backends

* Some code cleanup in _simulator.py

* Change autoreplacer priority for control. Added Additional test for autoreplacer. Added check for canonical ctrl state func.

* Update projectq/setups/default.py

* Update projectq/setups/decompositions/cnu2toffoliandcu.py

* Update projectq/setups/decompositions/cnu2toffoliandcu.py

* Cleanup code in _replacer.py

* Tweak some of the unit tests + add comments

* Add more tests for canonical_ctrl_state and has_negative_control

* Short pass of reformatting using black

* Bug fixing for rebasing

* Reformat files. Improve control_tester examples. Update change log

* Dummy change to trigger CI with new state

* Use pytest-mock for awsbraket client testing

* Fix Linter warnings

* Use pytest-mock also for awsbraket backend tests

* Fix missing tests in backends and added support for IonQ

* Fix linter warning

* Add support for AWSBraketBackend

* Fix small typo

* Use backported mock instead of unittest.mock

* Sort requirements_tests.txt

* Fix a bunch of errors that happens at program exit

 Monkeypatching or patching of external may unload the patch before
 the MainEngine calls the last flush operations which would then call
 the original API although unwanted.

Co-authored-by: Damien Nguyen <ngn.damien@gmail.com>
  • Loading branch information
XYShe and Takishima committed Jun 16, 2021
1 parent b078f67 commit aa3afaf
Show file tree
Hide file tree
Showing 30 changed files with 736 additions and 173 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ dmypy.json
*.out
*.app

# Others
err.txt

# ==============================================================================

VERSION.txt
Expand All @@ -180,3 +183,5 @@ thumbs.db

# Mac OSX artifacts
*.DS_Store

# ==============================================================================
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added ``pyproject.toml`` and ``setup.cfg``
- Added CHANGELOG.md
- Added backend for IonQ.
- Added support for state-dependent qubit control

### Deprecated

Expand Down
89 changes: 89 additions & 0 deletions examples/control_tester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
# Copyright 2021 ProjectQ-Framework (www.projectq.ch)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from projectq.cengines import MainEngine
from projectq.meta import Control
from projectq.ops import All, X, Measure, CtrlAll


def run_circuit(eng, circuit_num):
qubit = eng.allocate_qureg(2)
ctrl_fail = eng.allocate_qureg(3)
ctrl_success = eng.allocate_qureg(3)

if circuit_num == 1:
with Control(eng, ctrl_fail):
X | qubit[0]
All(X) | ctrl_success
with Control(eng, ctrl_success):
X | qubit[1]

elif circuit_num == 2:
All(X) | ctrl_fail
with Control(eng, ctrl_fail, ctrl_state=CtrlAll.Zero):
X | qubit[0]
with Control(eng, ctrl_success, ctrl_state=CtrlAll.Zero):
X | qubit[1]

elif circuit_num == 3:
All(X) | ctrl_fail
with Control(eng, ctrl_fail, ctrl_state='101'):
X | qubit[0]

X | ctrl_success[0]
X | ctrl_success[2]
with Control(eng, ctrl_success, ctrl_state='101'):
X | qubit[1]

elif circuit_num == 4:
All(X) | ctrl_fail
with Control(eng, ctrl_fail, ctrl_state=5):
X | qubit[0]

X | ctrl_success[0]
X | ctrl_success[2]
with Control(eng, ctrl_success, ctrl_state=5):
X | qubit[1]

All(Measure) | qubit
All(Measure) | ctrl_fail
All(Measure) | ctrl_success
eng.flush()
return qubit, ctrl_fail, ctrl_success


if __name__ == '__main__':
# Create a MainEngine with a unitary simulator backend
eng = MainEngine()

# Run out quantum circuit
# 1 - Default behaviour of the control: all control qubits should be 1
# 2 - Off-control: all control qubits should remain 0
# 3 - Specific state given by a string
# 4 - Specific state given by an integer

qubit, ctrl_fail, ctrl_success = run_circuit(eng, 4)

# Measured value of the failed qubit should be 0 in all cases
print('The final value of the qubit with failed control is:')
print(int(qubit[0]))
print('with the state of control qubits are:')
print([int(qubit) for qubit in ctrl_fail], '\n')

# Measured value of the success qubit should be 1 in all cases
print('The final value of the qubit with successful control is:')
print(int(qubit[1]))
print('with the state of control qubits are:')
print([int(qubit) for qubit in ctrl_success], '\n')
10 changes: 7 additions & 3 deletions projectq/backends/_aqt/_aqt_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from projectq import MainEngine
from projectq.backends._aqt import _aqt
from projectq.types import WeakQubitRef, Qubit
from projectq.types import WeakQubitRef
from projectq.cengines import DummyEngine, BasicMapperEngine
from projectq.ops import (
All,
Expand Down Expand Up @@ -140,6 +140,10 @@ def test_aqt_too_many_runs():
Rx(math.pi / 2) | qubit
eng.flush()

# Avoid exception at deletion
backend._num_runs = 1
backend._circuit = []


def test_aqt_retrieve(monkeypatch):
# patch send
Expand Down Expand Up @@ -171,7 +175,7 @@ def mock_retrieve(*args, **kwargs):
assert prob_dict['00'] == pytest.approx(0.6)

# Unknown qubit and no mapper
invalid_qubit = [Qubit(eng, 10)]
invalid_qubit = [WeakQubitRef(eng, 10)]
with pytest.raises(RuntimeError):
eng.backend.get_probabilities(invalid_qubit)

Expand Down Expand Up @@ -227,7 +231,7 @@ def mock_send(*args, **kwargs):
assert prob_dict['00'] == pytest.approx(0.6)

# Unknown qubit and no mapper
invalid_qubit = [Qubit(eng, 10)]
invalid_qubit = [WeakQubitRef(eng, 10)]
with pytest.raises(RuntimeError):
eng.backend.get_probabilities(invalid_qubit)

Expand Down
24 changes: 17 additions & 7 deletions projectq/backends/_awsbraket/_awsbraket.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import json

from projectq.cengines import BasicEngine
from projectq.meta import get_control_count, LogicalQubitIDTag
from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control
from projectq.types import WeakQubitRef
from projectq.ops import (
R,
Expand Down Expand Up @@ -176,6 +176,9 @@ def is_available(self, cmd):
if gate in (Measure, Allocate, Deallocate, Barrier):
return True

if has_negative_control(cmd):
return False

if self.device == 'Aspen-8':
if get_control_count(cmd) == 2:
return isinstance(gate, XGate)
Expand Down Expand Up @@ -271,21 +274,24 @@ def _store(self, cmd):
Args:
cmd: Command to store
"""
gate = cmd.gate

# Do not clear the self._clear flag for those gates
if gate in (Deallocate, Barrier):
return

num_controls = get_control_count(cmd)
gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate)

if self._clear:
self._probabilities = dict()
self._clear = False
self._circuit = ""
self._allocated_qubits = set()

gate = cmd.gate
num_controls = get_control_count(cmd)
gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate)

if gate == Allocate:
self._allocated_qubits.add(cmd.qubits[0][0].id)
return
if gate in (Deallocate, Barrier):
return
if gate == Measure:
assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1
qb_id = cmd.qubits[0][0].id
Expand Down Expand Up @@ -412,6 +418,10 @@ def _run(self):
# Also, AWS Braket currently does not support intermediate
# measurements.

# If the clear flag is set, nothing to do here...
if self._clear:
return

# In Braket the results for the jobs are stored in S3.
# You can recover the results from previous jobs using the TaskArn
# (self._retrieve_execution).
Expand Down
45 changes: 23 additions & 22 deletions projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
""" Test for projectq.backends._awsbraket._awsbraket_boto3_client.py """

import pytest
from unittest.mock import patch

from ._awsbraket_boto3_client_test_fixtures import * # noqa: F401,F403

Expand All @@ -34,13 +33,13 @@


@has_boto3
@patch('boto3.client')
def test_show_devices(mock_boto3_client, show_devices_setup):
def test_show_devices(mocker, show_devices_setup):
creds, search_value, device_value, devicelist_result = show_devices_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device'])
mock_boto3_client.search_devices.return_value = search_value
mock_boto3_client.get_device.return_value = device_value
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

devicelist = _awsbraket_boto3_client.show_devices(credentials=creds)
assert devicelist == devicelist_result
Expand Down Expand Up @@ -85,7 +84,6 @@ def test_show_devices(mock_boto3_client, show_devices_setup):


@has_boto3
@patch('boto3.client')
@pytest.mark.parametrize(
"var_status, var_result",
[
Expand All @@ -95,13 +93,14 @@ def test_show_devices(mock_boto3_client, show_devices_setup):
('other', other_value),
],
)
def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup):
def test_retrieve(mocker, var_status, var_result, retrieve_setup):
arntask, creds, device_value, res_completed, results_dict = retrieve_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object'])
mock_boto3_client.get_quantum_task.return_value = var_result
mock_boto3_client.get_device.return_value = device_value
mock_boto3_client.get_object.return_value = results_dict
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

if var_status == 'completed':
res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask)
Expand Down Expand Up @@ -132,8 +131,7 @@ def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup):


@has_boto3
@patch('boto3.client')
def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup):
def test_retrieve_devicetypes(mocker, retrieve_devicetypes_setup):
(
arntask,
creds,
Expand All @@ -142,10 +140,11 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup):
res_completed,
) = retrieve_devicetypes_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object'])
mock_boto3_client.get_quantum_task.return_value = completed_value
mock_boto3_client.get_device.return_value = device_value
mock_boto3_client.get_object.return_value = results_dict
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask)
assert res == res_completed
Expand All @@ -155,13 +154,13 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup):


@has_boto3
@patch('boto3.client')
def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup):
def test_send_too_many_qubits(mocker, send_too_many_setup):
(creds, s3_folder, search_value, device_value, info_too_much) = send_too_many_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device'])
mock_boto3_client.search_devices.return_value = search_value
mock_boto3_client.get_device.return_value = device_value
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

with pytest.raises(_awsbraket_boto3_client.DeviceTooSmall):
_awsbraket_boto3_client.send(info_too_much, device='name2', credentials=creds, s3_folder=s3_folder)
Expand All @@ -171,7 +170,6 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup):


@has_boto3
@patch('boto3.client')
@pytest.mark.parametrize(
"var_status, var_result",
[
Expand All @@ -181,7 +179,7 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup):
('other', other_value),
],
)
def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_result, real_device_online_setup):
def test_send_real_device_online_verbose(mocker, var_status, var_result, real_device_online_setup):

(
qtarntask,
Expand All @@ -194,12 +192,15 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu
results_dict,
) = real_device_online_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(
spec=['search_devices', 'get_device', 'create_quantum_task', 'get_quantum_task', 'get_object']
)
mock_boto3_client.search_devices.return_value = search_value
mock_boto3_client.get_device.return_value = device_value
mock_boto3_client.create_quantum_task.return_value = qtarntask
mock_boto3_client.get_quantum_task.return_value = var_result
mock_boto3_client.get_object.return_value = results_dict
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

# This is a ficticios situation because the job will be always queued
# at the beginning. After that the status will change at some point in time
Expand Down Expand Up @@ -243,7 +244,6 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu


@has_boto3
@patch('boto3.client')
@pytest.mark.parametrize(
"var_error",
[
Expand All @@ -254,16 +254,17 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu
('ValidationException'),
],
)
def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_error_setup):
def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup):
creds, s3_folder, info, search_value, device_value = send_that_error_setup

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device', 'create_quantum_task'])
mock_boto3_client.search_devices.return_value = search_value
mock_boto3_client.get_device.return_value = device_value
mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError(
{"Error": {"Code": var_error, "Message": "Msg error for " + var_error}},
"create_quantum_task",
)
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

with pytest.raises(botocore.exceptions.ClientError):
_awsbraket_boto3_client.send(info, device='name2', credentials=creds, s3_folder=s3_folder, num_retries=2)
Expand All @@ -282,15 +283,15 @@ def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_err


@has_boto3
@patch('boto3.client')
@pytest.mark.parametrize("var_error", [('ResourceNotFoundException')])
def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, creds):
def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds):

mock_boto3_client.return_value = mock_boto3_client
mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task'])
mock_boto3_client.get_quantum_task.side_effect = botocore.exceptions.ClientError(
{"Error": {"Code": var_error, "Message": "Msg error for " + var_error}},
"get_quantum_task",
)
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)

with pytest.raises(botocore.exceptions.ClientError):
_awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask)
Expand Down

0 comments on commit aa3afaf

Please sign in to comment.