Skip to content

Commit aa3afaf

Browse files
XYSheTakishima
andauthored
Ctrl generalize (#400)
* 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>
1 parent b078f67 commit aa3afaf

30 files changed

+736
-173
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ dmypy.json
171171
*.out
172172
*.app
173173

174+
# Others
175+
err.txt
176+
174177
# ==============================================================================
175178

176179
VERSION.txt
@@ -180,3 +183,5 @@ thumbs.db
180183

181184
# Mac OSX artifacts
182185
*.DS_Store
186+
187+
# ==============================================================================

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Added ``pyproject.toml`` and ``setup.cfg``
2020
- Added CHANGELOG.md
2121
- Added backend for IonQ.
22+
- Added support for state-dependent qubit control
2223

2324
### Deprecated
2425

examples/control_tester.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2021 ProjectQ-Framework (www.projectq.ch)
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from projectq.cengines import MainEngine
17+
from projectq.meta import Control
18+
from projectq.ops import All, X, Measure, CtrlAll
19+
20+
21+
def run_circuit(eng, circuit_num):
22+
qubit = eng.allocate_qureg(2)
23+
ctrl_fail = eng.allocate_qureg(3)
24+
ctrl_success = eng.allocate_qureg(3)
25+
26+
if circuit_num == 1:
27+
with Control(eng, ctrl_fail):
28+
X | qubit[0]
29+
All(X) | ctrl_success
30+
with Control(eng, ctrl_success):
31+
X | qubit[1]
32+
33+
elif circuit_num == 2:
34+
All(X) | ctrl_fail
35+
with Control(eng, ctrl_fail, ctrl_state=CtrlAll.Zero):
36+
X | qubit[0]
37+
with Control(eng, ctrl_success, ctrl_state=CtrlAll.Zero):
38+
X | qubit[1]
39+
40+
elif circuit_num == 3:
41+
All(X) | ctrl_fail
42+
with Control(eng, ctrl_fail, ctrl_state='101'):
43+
X | qubit[0]
44+
45+
X | ctrl_success[0]
46+
X | ctrl_success[2]
47+
with Control(eng, ctrl_success, ctrl_state='101'):
48+
X | qubit[1]
49+
50+
elif circuit_num == 4:
51+
All(X) | ctrl_fail
52+
with Control(eng, ctrl_fail, ctrl_state=5):
53+
X | qubit[0]
54+
55+
X | ctrl_success[0]
56+
X | ctrl_success[2]
57+
with Control(eng, ctrl_success, ctrl_state=5):
58+
X | qubit[1]
59+
60+
All(Measure) | qubit
61+
All(Measure) | ctrl_fail
62+
All(Measure) | ctrl_success
63+
eng.flush()
64+
return qubit, ctrl_fail, ctrl_success
65+
66+
67+
if __name__ == '__main__':
68+
# Create a MainEngine with a unitary simulator backend
69+
eng = MainEngine()
70+
71+
# Run out quantum circuit
72+
# 1 - Default behaviour of the control: all control qubits should be 1
73+
# 2 - Off-control: all control qubits should remain 0
74+
# 3 - Specific state given by a string
75+
# 4 - Specific state given by an integer
76+
77+
qubit, ctrl_fail, ctrl_success = run_circuit(eng, 4)
78+
79+
# Measured value of the failed qubit should be 0 in all cases
80+
print('The final value of the qubit with failed control is:')
81+
print(int(qubit[0]))
82+
print('with the state of control qubits are:')
83+
print([int(qubit) for qubit in ctrl_fail], '\n')
84+
85+
# Measured value of the success qubit should be 1 in all cases
86+
print('The final value of the qubit with successful control is:')
87+
print(int(qubit[1]))
88+
print('with the state of control qubits are:')
89+
print([int(qubit) for qubit in ctrl_success], '\n')

projectq/backends/_aqt/_aqt_test.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from projectq import MainEngine
2121
from projectq.backends._aqt import _aqt
22-
from projectq.types import WeakQubitRef, Qubit
22+
from projectq.types import WeakQubitRef
2323
from projectq.cengines import DummyEngine, BasicMapperEngine
2424
from projectq.ops import (
2525
All,
@@ -140,6 +140,10 @@ def test_aqt_too_many_runs():
140140
Rx(math.pi / 2) | qubit
141141
eng.flush()
142142

143+
# Avoid exception at deletion
144+
backend._num_runs = 1
145+
backend._circuit = []
146+
143147

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

173177
# Unknown qubit and no mapper
174-
invalid_qubit = [Qubit(eng, 10)]
178+
invalid_qubit = [WeakQubitRef(eng, 10)]
175179
with pytest.raises(RuntimeError):
176180
eng.backend.get_probabilities(invalid_qubit)
177181

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

229233
# Unknown qubit and no mapper
230-
invalid_qubit = [Qubit(eng, 10)]
234+
invalid_qubit = [WeakQubitRef(eng, 10)]
231235
with pytest.raises(RuntimeError):
232236
eng.backend.get_probabilities(invalid_qubit)
233237

projectq/backends/_awsbraket/_awsbraket.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import json
1919

2020
from projectq.cengines import BasicEngine
21-
from projectq.meta import get_control_count, LogicalQubitIDTag
21+
from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control
2222
from projectq.types import WeakQubitRef
2323
from projectq.ops import (
2424
R,
@@ -176,6 +176,9 @@ def is_available(self, cmd):
176176
if gate in (Measure, Allocate, Deallocate, Barrier):
177177
return True
178178

179+
if has_negative_control(cmd):
180+
return False
181+
179182
if self.device == 'Aspen-8':
180183
if get_control_count(cmd) == 2:
181184
return isinstance(gate, XGate)
@@ -271,21 +274,24 @@ def _store(self, cmd):
271274
Args:
272275
cmd: Command to store
273276
"""
277+
gate = cmd.gate
278+
279+
# Do not clear the self._clear flag for those gates
280+
if gate in (Deallocate, Barrier):
281+
return
282+
283+
num_controls = get_control_count(cmd)
284+
gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate)
285+
274286
if self._clear:
275287
self._probabilities = dict()
276288
self._clear = False
277289
self._circuit = ""
278290
self._allocated_qubits = set()
279291

280-
gate = cmd.gate
281-
num_controls = get_control_count(cmd)
282-
gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate)
283-
284292
if gate == Allocate:
285293
self._allocated_qubits.add(cmd.qubits[0][0].id)
286294
return
287-
if gate in (Deallocate, Barrier):
288-
return
289295
if gate == Measure:
290296
assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1
291297
qb_id = cmd.qubits[0][0].id
@@ -412,6 +418,10 @@ def _run(self):
412418
# Also, AWS Braket currently does not support intermediate
413419
# measurements.
414420

421+
# If the clear flag is set, nothing to do here...
422+
if self._clear:
423+
return
424+
415425
# In Braket the results for the jobs are stored in S3.
416426
# You can recover the results from previous jobs using the TaskArn
417427
# (self._retrieve_execution).

projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
""" Test for projectq.backends._awsbraket._awsbraket_boto3_client.py """
1616

1717
import pytest
18-
from unittest.mock import patch
1918

2019
from ._awsbraket_boto3_client_test_fixtures import * # noqa: F401,F403
2120

@@ -34,13 +33,13 @@
3433

3534

3635
@has_boto3
37-
@patch('boto3.client')
38-
def test_show_devices(mock_boto3_client, show_devices_setup):
36+
def test_show_devices(mocker, show_devices_setup):
3937
creds, search_value, device_value, devicelist_result = show_devices_setup
4038

41-
mock_boto3_client.return_value = mock_boto3_client
39+
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device'])
4240
mock_boto3_client.search_devices.return_value = search_value
4341
mock_boto3_client.get_device.return_value = device_value
42+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
4443

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

8685

8786
@has_boto3
88-
@patch('boto3.client')
8987
@pytest.mark.parametrize(
9088
"var_status, var_result",
9189
[
@@ -95,13 +93,14 @@ def test_show_devices(mock_boto3_client, show_devices_setup):
9593
('other', other_value),
9694
],
9795
)
98-
def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup):
96+
def test_retrieve(mocker, var_status, var_result, retrieve_setup):
9997
arntask, creds, device_value, res_completed, results_dict = retrieve_setup
10098

101-
mock_boto3_client.return_value = mock_boto3_client
99+
mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object'])
102100
mock_boto3_client.get_quantum_task.return_value = var_result
103101
mock_boto3_client.get_device.return_value = device_value
104102
mock_boto3_client.get_object.return_value = results_dict
103+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
105104

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

133132

134133
@has_boto3
135-
@patch('boto3.client')
136-
def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup):
134+
def test_retrieve_devicetypes(mocker, retrieve_devicetypes_setup):
137135
(
138136
arntask,
139137
creds,
@@ -142,10 +140,11 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup):
142140
res_completed,
143141
) = retrieve_devicetypes_setup
144142

145-
mock_boto3_client.return_value = mock_boto3_client
143+
mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object'])
146144
mock_boto3_client.get_quantum_task.return_value = completed_value
147145
mock_boto3_client.get_device.return_value = device_value
148146
mock_boto3_client.get_object.return_value = results_dict
147+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
149148

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

156155

157156
@has_boto3
158-
@patch('boto3.client')
159-
def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup):
157+
def test_send_too_many_qubits(mocker, send_too_many_setup):
160158
(creds, s3_folder, search_value, device_value, info_too_much) = send_too_many_setup
161159

162-
mock_boto3_client.return_value = mock_boto3_client
160+
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device'])
163161
mock_boto3_client.search_devices.return_value = search_value
164162
mock_boto3_client.get_device.return_value = device_value
163+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
165164

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

172171

173172
@has_boto3
174-
@patch('boto3.client')
175173
@pytest.mark.parametrize(
176174
"var_status, var_result",
177175
[
@@ -181,7 +179,7 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup):
181179
('other', other_value),
182180
],
183181
)
184-
def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_result, real_device_online_setup):
182+
def test_send_real_device_online_verbose(mocker, var_status, var_result, real_device_online_setup):
185183

186184
(
187185
qtarntask,
@@ -194,12 +192,15 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu
194192
results_dict,
195193
) = real_device_online_setup
196194

197-
mock_boto3_client.return_value = mock_boto3_client
195+
mock_boto3_client = mocker.MagicMock(
196+
spec=['search_devices', 'get_device', 'create_quantum_task', 'get_quantum_task', 'get_object']
197+
)
198198
mock_boto3_client.search_devices.return_value = search_value
199199
mock_boto3_client.get_device.return_value = device_value
200200
mock_boto3_client.create_quantum_task.return_value = qtarntask
201201
mock_boto3_client.get_quantum_task.return_value = var_result
202202
mock_boto3_client.get_object.return_value = results_dict
203+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
203204

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

244245

245246
@has_boto3
246-
@patch('boto3.client')
247247
@pytest.mark.parametrize(
248248
"var_error",
249249
[
@@ -254,16 +254,17 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu
254254
('ValidationException'),
255255
],
256256
)
257-
def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_error_setup):
257+
def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup):
258258
creds, s3_folder, info, search_value, device_value = send_that_error_setup
259259

260-
mock_boto3_client.return_value = mock_boto3_client
260+
mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device', 'create_quantum_task'])
261261
mock_boto3_client.search_devices.return_value = search_value
262262
mock_boto3_client.get_device.return_value = device_value
263263
mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError(
264264
{"Error": {"Code": var_error, "Message": "Msg error for " + var_error}},
265265
"create_quantum_task",
266266
)
267+
mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True)
267268

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

283284

284285
@has_boto3
285-
@patch('boto3.client')
286286
@pytest.mark.parametrize("var_error", [('ResourceNotFoundException')])
287-
def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, creds):
287+
def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds):
288288

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

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

0 commit comments

Comments
 (0)