Skip to content

Commit 0d5e491

Browse files
committed
Add ProjectQ -> OpenQASM conversion
1 parent 475bd57 commit 0d5e491

File tree

2 files changed

+627
-0
lines changed

2 files changed

+627
-0
lines changed

projectq/backends/_qasm.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# Copyright 2020 ProjectQ-Framework (www.projectq.ch)
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
""" Backend to convert ProjectQ commands to OpenQASM. """
15+
16+
from copy import deepcopy
17+
18+
from projectq.cengines import BasicEngine
19+
from projectq.meta import get_control_count
20+
from projectq.ops import (X, NOT, Y, Z, T, Tdag, S, Sdag, H, Ph, R, Rx, Ry, Rz,
21+
Swap, Measure, Allocate, Deallocate, Barrier,
22+
FlushGate)
23+
24+
# ==============================================================================
25+
26+
27+
class OpenQASMBackend(BasicEngine):
28+
"""
29+
Engine to convert ProjectQ commands to OpenQASM format (either string or
30+
file)
31+
"""
32+
def __init__(self,
33+
collate=True,
34+
collate_callback=None,
35+
qubit_callback=lambda qubit_id: 'q{}'.format(qubit_id),
36+
bit_callback=lambda qubit_id: 'c{}'.format(qubit_id),
37+
qubit_id_mapping_redux=True):
38+
"""
39+
Initialize an OpenQASMBackend object.
40+
41+
Contrary to OpenQASM, ProjectQ does not impose the restriction that a
42+
programm must start with qubit/bit allocations and end with some
43+
measurements.
44+
45+
The user can configure what happens each time a FlushGate() is
46+
encountered by setting the `collate` and `collate_func` arguments to
47+
an OpenQASMBackend constructor,
48+
49+
Args:
50+
output (list,file):
51+
collate (bool): If True, simply append commands to the exisiting
52+
file/string list when a FlushGate is received. If False, you
53+
need to specify `collate_callback` arguments as well.
54+
collate (function): Only has an effect if `collate` is False. Each
55+
time a FlushGate is received, this callback function will be
56+
called.
57+
Function signature: Callable[[Sequence[str]], None]
58+
qubit_callback (function): Callback function called upon create of
59+
each qubit to generate a name for the qubit.
60+
Function signature: Callable[[int], str]
61+
bit_callback (function): Callback function called upon create of
62+
each qubit to generate a name for the qubit.
63+
Function signature: Callable[[int], str]
64+
qubit_id_mapping_redux (bool): If True, try to allocate new Qubit
65+
IDs to the next available qreg/creg (if any), otherwise create
66+
a new qreg/creg. If False, simply create a new qreg/creg for
67+
each new Qubit ID
68+
"""
69+
super().__init__()
70+
self._collate = collate
71+
self._collate_callback = None if collate else collate_callback
72+
self._gen_qubit_name = qubit_callback
73+
self._gen_bit_name = bit_callback
74+
self._qubit_id_mapping_redux = qubit_id_mapping_redux
75+
76+
self._output = []
77+
self._qreg_dict = dict()
78+
self._creg_dict = dict()
79+
self._reg_index = 0
80+
self._available_indices = []
81+
82+
self._insert_openqasm_header()
83+
84+
@property
85+
def qasm(self):
86+
return self._output
87+
88+
def is_available(self, cmd):
89+
"""
90+
Return true if the command can be executed.
91+
92+
Args:
93+
cmd (Command): Command for which to check availability
94+
"""
95+
gate = cmd.gate
96+
n_controls = get_control_count(cmd)
97+
98+
is_available = False
99+
100+
if gate in (Measure, Allocate, Deallocate, Barrier):
101+
is_available = True
102+
103+
if n_controls == 0:
104+
if gate in (H, S, Sdag, T, Tdag, X, NOT, Y, Z, Swap):
105+
is_available = True
106+
if isinstance(gate, (Ph, R, Rx, Ry, Rz)):
107+
is_available = True
108+
elif n_controls == 1:
109+
if gate in (H, X, NOT, Y, Z):
110+
is_available = True
111+
if isinstance(gate, (
112+
R,
113+
Rz,
114+
)):
115+
is_available = True
116+
elif n_controls == 2:
117+
if gate in (X, NOT):
118+
is_available = True
119+
120+
if not is_available:
121+
return False
122+
if not self.is_last_engine:
123+
return self.next_engine.is_available(cmd)
124+
else:
125+
return True
126+
127+
def receive(self, command_list):
128+
"""
129+
Receives a command list and, for each command, stores it until
130+
completion.
131+
132+
Args:
133+
command_list: List of commands to execute
134+
"""
135+
for cmd in command_list:
136+
if not cmd.gate == FlushGate():
137+
self._store(cmd)
138+
else:
139+
self._reset_after_flush()
140+
141+
if not self.is_last_engine:
142+
self.send(command_list)
143+
144+
def _store(self, cmd):
145+
"""
146+
Temporarily store the command cmd.
147+
148+
Translates the command and stores it the _openqasm_circuit attribute
149+
(self._openqasm_circuit)
150+
151+
Args:
152+
cmd: Command to store
153+
"""
154+
gate = cmd.gate
155+
n_controls = get_control_count(cmd)
156+
157+
def _format_angle(angle):
158+
return '({})'.format(angle)
159+
160+
_ccontrolled_gates_func = {
161+
X: 'ccx',
162+
NOT: 'ccx',
163+
}
164+
_controlled_gates_func = {
165+
H: 'ch',
166+
Ph: 'cu1',
167+
R: 'cu1',
168+
Rz: 'crz',
169+
X: 'cx',
170+
NOT: 'cx',
171+
Y: 'cy',
172+
Z: 'cz',
173+
Swap: 'cswap'
174+
}
175+
_gates_func = {
176+
Barrier: 'barrier',
177+
H: 'h',
178+
Ph: 'u1',
179+
S: 's',
180+
Sdag: 'sdg',
181+
T: 't',
182+
Tdag: 'tdg',
183+
R: 'u1',
184+
Rx: 'rx',
185+
Ry: 'ry',
186+
Rz: 'rz',
187+
X: 'x',
188+
NOT: 'x',
189+
Y: 'y',
190+
Z: 'z',
191+
Swap: 'swap'
192+
}
193+
194+
if gate == Allocate:
195+
add = True
196+
197+
# Perform qubit index reduction if possible. This typically means
198+
# that existing qubit keep their indices between FlushGates but
199+
# that qubit indices of deallocated qubit may be reused.
200+
if self._qubit_id_mapping_redux and self._available_indices:
201+
add = False
202+
index = self._available_indices.pop()
203+
else:
204+
index = self._reg_index
205+
self._reg_index += 1
206+
207+
qb_id = cmd.qubits[0][0].id
208+
209+
# TODO: only create bit for qubits that are actually measured
210+
self._qreg_dict[qb_id] = self._gen_qubit_name(index)
211+
self._creg_dict[qb_id] = self._gen_bit_name(index)
212+
213+
if add:
214+
self._output.append('qubit {};'.format(self._qreg_dict[qb_id]))
215+
self._output.append('bit {};'.format(self._creg_dict[qb_id]))
216+
217+
elif gate == Deallocate:
218+
qb_id = cmd.qubits[0][0].id
219+
220+
if self._qubit_id_mapping_redux:
221+
self._available_indices.append(qb_id)
222+
del self._qreg_dict[qb_id]
223+
del self._creg_dict[qb_id]
224+
225+
elif gate == Measure:
226+
assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1
227+
qb_id = cmd.qubits[0][0].id
228+
229+
self._output.append('{} = measure {};'.format(
230+
self._creg_dict[qb_id], self._qreg_dict[qb_id]))
231+
232+
elif n_controls == 2:
233+
targets = [
234+
self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg
235+
]
236+
controls = [self._qreg_dict[qb.id] for qb in cmd.control_qubits]
237+
238+
try:
239+
self._output.append('{} {};'.format(
240+
_ccontrolled_gates_func[gate],
241+
','.join(controls + targets)))
242+
except KeyError:
243+
raise RuntimeError(
244+
'Unable to perform {} gate with n=2 control qubits'.format(
245+
gate))
246+
247+
elif n_controls == 1:
248+
target_qureg = [
249+
self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg
250+
]
251+
252+
try:
253+
if isinstance(gate, Ph):
254+
self._output.append('{}{} {},{};'.format(
255+
_controlled_gates_func[type(gate)],
256+
_format_angle(-gate.angle / 2.),
257+
self._qreg_dict[cmd.control_qubits[0].id],
258+
target_qureg[0]))
259+
elif isinstance(gate, (
260+
R,
261+
Rz,
262+
)):
263+
self._output.append('{}{} {},{};'.format(
264+
_controlled_gates_func[type(gate)],
265+
_format_angle(gate.angle),
266+
self._qreg_dict[cmd.control_qubits[0].id],
267+
target_qureg[0]))
268+
else:
269+
self._output.append('{} {},{};'.format(
270+
_controlled_gates_func[gate],
271+
self._qreg_dict[cmd.control_qubits[0].id],
272+
*target_qureg))
273+
except KeyError:
274+
raise RuntimeError(
275+
'Unable to perform {} gate with n=1 control qubits'.format(
276+
gate))
277+
else:
278+
target_qureg = [
279+
self._qreg_dict[qb.id] for qureg in cmd.qubits for qb in qureg
280+
]
281+
if isinstance(gate, Ph):
282+
self._output.append('{}{} {};'.format(
283+
_gates_func[type(gate)], _format_angle(-gate.angle / 2.),
284+
target_qureg[0]))
285+
elif isinstance(gate, (R, Rx, Ry, Rz)):
286+
self._output.append('{}{} {};'.format(_gates_func[type(gate)],
287+
_format_angle(gate.angle),
288+
target_qureg[0]))
289+
else:
290+
self._output.append('{} {};'.format(_gates_func[gate],
291+
*target_qureg))
292+
293+
def _insert_openqasm_header(self):
294+
self._output.append('OPENQASM 3;')
295+
self._output.append('include "stdgates.inc";')
296+
297+
def _reset_after_flush(self):
298+
"""
299+
Reset the internal quantum circuit after a FlushGate
300+
"""
301+
if self._collate:
302+
self._output.append('# ' + '=' * 80)
303+
else:
304+
self._collate_callback(deepcopy(self._output))
305+
self._output.clear()
306+
self._insert_openqasm_header()
307+
for qubit_name in self._qreg_dict.values():
308+
self._output.append('qubit {};'.format(qubit_name))
309+
for bit_name in self._creg_dict.values():
310+
self._output.append('bit {};'.format(bit_name))

0 commit comments

Comments
 (0)