Skip to content

Commit e142a99

Browse files
Added new setups for mappers and restricted gate sets (ProjectQ-Framework#234)
1 parent 66bf235 commit e142a99

6 files changed

Lines changed: 804 additions & 0 deletions

File tree

projectq/setups/linear.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Copyright 2018 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+
15+
"""
16+
Defines a setup to compile to qubits placed in a linear chain or a circle.
17+
18+
It provides the `engine_list` for the `MainEngine`. This engine list contains
19+
an AutoReplacer with most of the gate decompositions of ProjectQ, which are
20+
used to decompose a circuit into only two qubit gates and arbitrary single
21+
qubit gates. ProjectQ's LinearMapper is then used to introduce the necessary
22+
Swap operations to route interacting qubits next to each other. This setup
23+
allows to choose the final gate set (with some limitations).
24+
"""
25+
26+
import inspect
27+
28+
import projectq
29+
import projectq.libs.math
30+
import projectq.setups.decompositions
31+
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
32+
InstructionFilter, LinearMapper, LocalOptimizer,
33+
TagRemover)
34+
from projectq.ops import (BasicMathGate, ClassicalInstructionGate, CNOT,
35+
ControlledGate, get_inverse, QFT, Swap)
36+
37+
38+
def high_level_gates(eng, cmd):
39+
"""
40+
Remove any MathGates.
41+
"""
42+
g = cmd.gate
43+
if g == QFT or get_inverse(g) == QFT or g == Swap:
44+
return True
45+
elif isinstance(g, BasicMathGate):
46+
return False
47+
return True
48+
49+
50+
def one_and_two_qubit_gates(eng, cmd):
51+
all_qubits = [q for qr in cmd.all_qubits for q in qr]
52+
if isinstance(cmd.gate, ClassicalInstructionGate):
53+
# This is required to allow Measure, Allocate, Deallocate, Flush
54+
return True
55+
elif len(all_qubits) <= 2:
56+
return True
57+
else:
58+
return False
59+
60+
61+
def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any",
62+
two_qubit_gates=(CNOT, Swap)):
63+
"""
64+
Returns an engine list to compile to a linear chain of qubits.
65+
66+
Note:
67+
If you choose a new gate set for which the compiler does not yet have
68+
standard rules, it raises an `NoGateDecompositionError` or a
69+
`RuntimeError: maximum recursion depth exceeded...`. Also note that
70+
even the gate sets which work might not yet be optimized. So make sure
71+
to double check and potentially extend the decomposition rules.
72+
This implemention currently requires that the one qubit gates must
73+
contain Rz and at least one of {Ry(best), Rx, H} and the two qubit gate
74+
must contain CNOT.
75+
76+
Note:
77+
Classical instructions gates such as e.g. Flush and Measure are
78+
automatically allowed.
79+
80+
Example:
81+
get_engine_list(num_qubits=10, cyclic=False,
82+
one_qubit_gates=(Rz, Ry, Rx, H),
83+
two_qubit_gates=(CNOT,))
84+
85+
Args:
86+
num_qubits(int): Number of qubits in the chain
87+
cyclic(bool): If a circle or not. Default is False
88+
one_qubit_gates: "any" allows any one qubit gate, otherwise provide
89+
a tuple of the allowed gates. If the gates are
90+
instances of a class (e.g. X), it allows all gates
91+
which are equal to it. If the gate is a class (Rz), it
92+
allows all instances of this class. Default is "any"
93+
two_qubit_gates: "any" allows any two qubit gate, otherwise provide
94+
a tuple of the allowed gates. If the gates are
95+
instances of a class (e.g. CNOT), it allows all gates
96+
which are equal to it. If the gate is a class, it
97+
allows all instances of this class.
98+
Default is (CNOT, Swap).
99+
Raises:
100+
TypeError: If input is for the gates is not "any" or a tuple.
101+
102+
Returns:
103+
A list of suitable compiler engines.
104+
"""
105+
if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple):
106+
raise TypeError("two_qubit_gates parameter must be 'any' or a tuple. "
107+
"When supplying only one gate, make sure to correctly "
108+
"create the tuple (don't miss the comma), "
109+
"e.g. two_qubit_gates=(CNOT,)")
110+
if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple):
111+
raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.")
112+
113+
rule_set = DecompositionRuleSet(modules=[projectq.libs.math,
114+
projectq.setups.decompositions])
115+
allowed_gate_classes = []
116+
allowed_gate_instances = []
117+
if one_qubit_gates != "any":
118+
for gate in one_qubit_gates:
119+
if inspect.isclass(gate):
120+
allowed_gate_classes.append(gate)
121+
else:
122+
allowed_gate_instances.append((gate, 0))
123+
if two_qubit_gates != "any":
124+
for gate in two_qubit_gates:
125+
if inspect.isclass(gate):
126+
# Controlled gate classes don't yet exists and would require
127+
# separate treatment
128+
assert not isinstance(gate, ControlledGate)
129+
allowed_gate_classes.append(gate)
130+
else:
131+
if isinstance(gate, ControlledGate):
132+
allowed_gate_instances.append((gate._gate, gate._n))
133+
else:
134+
allowed_gate_instances.append((gate, 0))
135+
allowed_gate_classes = tuple(allowed_gate_classes)
136+
allowed_gate_instances = tuple(allowed_gate_instances)
137+
138+
def low_level_gates(eng, cmd):
139+
all_qubits = [q for qr in cmd.all_qubits for q in qr]
140+
assert len(all_qubits) <= 2
141+
if isinstance(cmd.gate, ClassicalInstructionGate):
142+
# This is required to allow Measure, Allocate, Deallocate, Flush
143+
return True
144+
elif one_qubit_gates == "any" and len(all_qubits) == 1:
145+
return True
146+
elif two_qubit_gates == "any" and len(all_qubits) == 2:
147+
return True
148+
elif isinstance(cmd.gate, allowed_gate_classes):
149+
return True
150+
elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances:
151+
return True
152+
else:
153+
return False
154+
155+
return [AutoReplacer(rule_set),
156+
TagRemover(),
157+
InstructionFilter(high_level_gates),
158+
LocalOptimizer(5),
159+
AutoReplacer(rule_set),
160+
TagRemover(),
161+
InstructionFilter(one_and_two_qubit_gates),
162+
LocalOptimizer(5),
163+
LinearMapper(num_qubits=num_qubits, cyclic=cyclic),
164+
AutoReplacer(rule_set),
165+
TagRemover(),
166+
InstructionFilter(low_level_gates),
167+
LocalOptimizer(5),
168+
]

projectq/setups/linear_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright 2018 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+
15+
"""Tests for projectq.setups.linear."""
16+
17+
import pytest
18+
19+
import projectq
20+
from projectq.cengines import DummyEngine, LinearMapper
21+
from projectq.libs.math import AddConstant
22+
from projectq.ops import BasicGate, CNOT, H, Measure, Rx, Rz, Swap, X
23+
24+
import projectq.setups.linear as linear_setup
25+
26+
27+
def test_mapper_present_and_correct_params():
28+
found = False
29+
mapper = None
30+
for engine in linear_setup.get_engine_list(num_qubits=10, cyclic=True):
31+
if isinstance(engine, LinearMapper):
32+
mapper = engine
33+
found = True
34+
assert found
35+
assert mapper.num_qubits == 10
36+
assert mapper.cyclic
37+
38+
39+
def test_parameter_any():
40+
engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False,
41+
one_qubit_gates="any",
42+
two_qubit_gates="any")
43+
backend = DummyEngine(save_commands=True)
44+
eng = projectq.MainEngine(backend, engine_list)
45+
qubit1 = eng.allocate_qubit()
46+
qubit2 = eng.allocate_qubit()
47+
gate = BasicGate()
48+
gate | (qubit1, qubit2)
49+
gate | qubit1
50+
eng.flush()
51+
print(len(backend.received_commands))
52+
assert backend.received_commands[2].gate == gate
53+
assert backend.received_commands[3].gate == gate
54+
55+
56+
def test_restriction():
57+
engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False,
58+
one_qubit_gates=(Rz, H),
59+
two_qubit_gates=(CNOT,
60+
AddConstant))
61+
backend = DummyEngine(save_commands=True)
62+
eng = projectq.MainEngine(backend, engine_list)
63+
qubit1 = eng.allocate_qubit()
64+
qubit2 = eng.allocate_qubit()
65+
qubit3 = eng.allocate_qubit()
66+
eng.flush()
67+
CNOT | (qubit1, qubit2)
68+
H | qubit1
69+
Rz(0.2) | qubit1
70+
Measure | qubit1
71+
Swap | (qubit1, qubit2)
72+
Rx(0.1) | (qubit1)
73+
AddConstant(1) | qubit1 + qubit2 + qubit3
74+
eng.flush()
75+
assert backend.received_commands[4].gate == X
76+
assert len(backend.received_commands[4].control_qubits) == 1
77+
assert backend.received_commands[5].gate == H
78+
assert backend.received_commands[6].gate == Rz(0.2)
79+
assert backend.received_commands[7].gate == Measure
80+
for cmd in backend.received_commands[7:]:
81+
assert cmd.gate != Swap
82+
assert not isinstance(cmd.gate, Rx)
83+
assert not isinstance(cmd.gate, AddConstant)
84+
85+
86+
def test_wrong_init():
87+
with pytest.raises(TypeError):
88+
engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False,
89+
one_qubit_gates="any",
90+
two_qubit_gates=(CNOT))
91+
with pytest.raises(TypeError):
92+
engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False,
93+
one_qubit_gates="Any",
94+
two_qubit_gates=(CNOT,))

0 commit comments

Comments
 (0)