├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── builtin_arithmetics.py ├── builtin_arithmetics_test.py ├── builtin_gates.py ├── qfourier.py ├── qfourier_test.py ├── qgrover.py ├── qgrover_test.py ├── quasar.py ├── quasar_ast.py ├── quasar_cmd.py ├── quasar_comp.py ├── quasar_formatter.py ├── quasar_opt.py ├── quasar_opt_test.py ├── quasar_qasm.py ├── quasar_qasm_test.py ├── quasar_qiskit.py ├── quasar_qiskit_test.py └── qutils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 2 | ---- 3 | 4 | # Changelog 5 | 6 | ## 1.0.2 7 | ### Breaking compatibility changes 8 | - `Swap` function has been moved from utils.py to quasar.py file 9 | - `keep_comments` argument of `Quasar.compile` function has been removed 10 | - `qasm_formatter` argument of `Quasar.compile` has no more default argument set to `QiskitFormatter` 11 | 12 | ### New features 13 | - New gates `Sdg`, `Tdg` have been implemented 14 | - Optimizations for a generated circuit. It can be turn on with `optimize` argument of `Quasar.compile` function 15 | - New file `quasar_qasm.py` with support for OpenQASM code generation 16 | - New file `quqsar_qiskit.py` with support for IBM Qiskit library 17 | - New file `quasar_opt.py` with circuit gates optimization 18 | 19 | ## 1.0.1 20 | ### Breaking compatibility changes 21 | - `QBit` has been renamed to `Qubit` 22 | - `Program.QBit` has been renamed to `Program.Qubit` 23 | - `Program.QBits` has been renamed to `Program.Qubits` 24 | 25 | ### Bug fixes 26 | - Fix for `_Program._accept` implementation 27 | - Fix in `Fourier` example. Use `Phase` gate insted of `RZ` one. 28 | 29 | ### New features 30 | - New gate [U3]`(phi, theta, lambda)` has been implemented 31 | - New gates `Id`, `RX`, `CRX`, `RY`, `CRY`, `RZ`, `CRZ`, `Phase` have been implemented 32 | - New file `qfourier.py` with Fourier algorithm implementation 33 | - New file `qgrover.py` with Grover algorithm implementation 34 | - New file `qutils.py` with: 35 | - `Set(qs: List[Qubit], value: int) -> Program` - set clear register to specific little-endian unsigned value. 36 | - `Inc(qs: List[Qubit]) -> Program` - increment little-endian unsigned value 37 | - `Dec(qs: List[Qubit]) -> Program` - decrement little-endian unsigned value 38 | - `Swap(qs1: Union[Qubit, List[Qubit]], qs2: Union[Qubit, List[Qubit]]) -> Program` - swap two registers 39 | - `Equal(qs1: Union[Qubit, List[Qubit]], qs1: Union[Qubit, List[Qubit]]) -> Program` - check if two registers are equal 40 | 41 | 42 | [U3]: https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/extensions/standard/u3.py 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 2 | ---- 3 | 4 | # Quasar 5 | 6 | Quasar is a utility tool to simplify writing quantum assembly code. It helps translate high level `if` statements into a sequence of controlled quantum commands. It can be easily integrated with [IBM Qiskit], [Google Cirq] or [OpenQASM]. 7 | 8 | Express your thoughts in a high-level way, let Quasar take care about details. 9 | 10 | # Features 11 | 12 | At the beginning of each program the `Program` object has to be created 13 | 14 | ``` 15 | prgm = Program() 16 | ``` 17 | 18 | Single qubit object can be created as follow 19 | 20 | ``` 21 | qubit_0 = prgm.Qubit() 22 | ``` 23 | 24 | An initial value for a qubit can also be provided 25 | 26 | ``` 27 | qubit_1 = prgm.Qubit( 1 ) 28 | ``` 29 | 30 | Many qubits can be created in one statement 31 | 32 | ``` 33 | qubits = prgm.Qubits( [ 0, 1, 0, 1, 0, 1, 0, 1 ] ) 34 | ``` 35 | 36 | Instructions are composed either with `+=` and `+` operators 37 | 38 | ``` 39 | prgm += X( qubit_0 ) + Y( qubit_1 ) + Z( qubits[0] ) 40 | ``` 41 | 42 | Or with a list syntax 43 | 44 | ``` 45 | prgm += [ X( qubit_0 ) , H( qubit_1 ) , Z( qubits[0] ) ] 46 | ``` 47 | 48 | Quasar simplifies writing `if` statements. It looks almost like in any high-level programming language 49 | 50 | ``` 51 | If( ... ).Then( ... ) 52 | If( ... ).Then( ... ).Else( ... ) 53 | ``` 54 | 55 | For example 56 | 57 | ``` 58 | If( All( qubit_1 ) ).Then( 59 | X( qubit_0 ) 60 | ) 61 | ``` 62 | 63 | Inside an `if` statements some functions can be used 64 | 65 | `Any( qubits: List[ Qubit ] ) -> Bool`, at least one qubit has to be set to 1 66 | 67 | ``` 68 | If( Any( qubits[0], qubits[1], qubits[2] ) ) 69 | ``` 70 | 71 | `Zero( qubits: List[ Qubit ] ) -> Bool`, all qubits have to be set to 0 72 | 73 | ``` 74 | If( Zero( qubits[0], qubits[1], qubits[2] ) ) 75 | ``` 76 | 77 | `All( qubits: List[ Qubit ] ) -> Bool`, all qubits has to be set to 1 78 | 79 | ``` 80 | If( All( qubits[0], qubits[1], qubits[2] ) ) 81 | ``` 82 | 83 | `Match( qubits : List[ Qubit ], mask: List[int] ) -> Bool`, all qubits has to be set as in a mask 84 | 85 | ``` 86 | If( Match( [qubits[0], qubits[1], qubits[2], qubits[3]], mask=[1, 0, 1, 0] ) ) 87 | ``` 88 | 89 | To simplify writing Grover algorithm `If(...).Flip()` statement is provided. `Flip` make phase inversion for all states which satisfy the condition. 90 | 91 | ``` 92 | If( All( qubits[0], qubits[1], qubits[2] ).Flip() 93 | ``` 94 | 95 | # Installation and Requirements 96 | 97 | Quasar is a single python file script. It does not have any non-standard dependencies. It is required to have python >= 3.7 98 | 99 | # Extension 100 | 101 | To generate an assembly code in your own format just define an implementation of the `IQAsmFormatter` class 102 | 103 | # Quasar to IBM Qiskit intagration 104 | 105 | ```python 106 | # Quasar 107 | from quasar import All, H, If, Program, Quasar, X 108 | 109 | prgm = Program() 110 | q_bits = prgm.Qubits([0, 0]) 111 | c_bits = prgm.CBits(2) 112 | 113 | prgm += X(q_bits[0]) 114 | prgm += If(All(q_bits[0])).Then( 115 | H(q_bits[1]) 116 | ) 117 | 118 | # Quasar -> OpenQASM 119 | qasm_str = Quasar().to_qasm_str(prgm) 120 | 121 | # OpenQASM -> Qiskit 122 | from qiskit import execute, Aer, QuantumCircuit 123 | circuit = QuantumCircuit.from_qasm_str(qasm_str) 124 | result = execute(circuit, Aer.get_backend('statevector_simulator')).result() 125 | print(result.get_statevector(circuit, decimals=3)) 126 | ``` 127 | 128 | # Syntax examples 129 | 130 | ```python 131 | from quasar import All, If, Not, Program, Quasar, X 132 | 133 | def example_nested_if() -> None: 134 | prgm = Program() 135 | 136 | q_vars = prgm.Qubits(32 * [0]) 137 | 138 | prgm += If(All([q_vars[1], q_vars[2], q_vars[3], q_vars[4]])).Then( 139 | X(q_vars[3]) + 140 | If(Not(All(q_vars[4]))).Then( 141 | X(q_vars[5]) + X(q_vars[6]) 142 | ).Else( 143 | If(All(q_vars[7])).Then( 144 | [X(q_vars[8]), X(q_vars[9])] 145 | ).Else( 146 | [X(q_vars[10]), X(q_vars[11])] 147 | ) 148 | ) 149 | ).Else( 150 | If(Not(All(q_vars[11]))).Then( 151 | X(q_vars[12]) + X(q_vars[13]) 152 | ).Else( 153 | If(All(q_vars[14])).Then( 154 | X(q_vars[15]) + 155 | X(q_vars[16]) + 156 | If(Not(All(q_vars[17]))).Then( 157 | X(q_vars[18]) + X(q_vars[19]) 158 | ).Else( 159 | If(All(q_vars[20])).Then( 160 | [X(q_vars[21]), X(q_vars[22])] 161 | ).Else( 162 | [X(q_vars[23]), X(q_vars[24])] 163 | ) 164 | ) 165 | ).Else( 166 | If(Not(All(q_vars[25]))).Then( 167 | X(q_vars[26]) + X(q_vars[27]) 168 | ).Else( 169 | If(All(q_vars[28])).Then( 170 | [X(q_vars[29]), X(q_vars[30])] 171 | ).Else( 172 | [X(q_vars[31])] 173 | ) 174 | ) 175 | ) 176 | ) 177 | ) 178 | 179 | print(Quasar().to_qasm_str(prgm)) 180 | 181 | example_nested_if() 182 | ``` 183 | 184 | ```python 185 | from quasar import All, CX, If, Measurement, Program, Quasar 186 | 187 | def example_measurement() -> None: 188 | prgm = Program() 189 | 190 | q_vars = prgm.Qubits([1, 0, 1, 0, 0]) 191 | c_vars = prgm.CBits(5) 192 | 193 | prgm += ( 194 | CX(q_vars[0], q_vars[1]) + 195 | If(All(q_vars[1])).Then( 196 | If(All(q_vars[2])).Then( 197 | CX(q_vars[3], q_vars[4]) 198 | ) 199 | ) + 200 | Measurement(q_vars[0], c_vars[0]) 201 | ) 202 | 203 | print(Quasar().to_qasm_str(prgm)) 204 | 205 | example_measurement() 206 | ``` 207 | 208 | ```python 209 | from typing import Callable, List 210 | from math import asin, pi, sqrt 211 | 212 | from quasar import All, ASTNode, CX, Flip, H, If, Match, Measurement, Program, Quasar, Qubit, Zero 213 | 214 | def example_grover() -> None: 215 | def Grover( 216 | qubits: List[Qubit], 217 | predicate: ASTNode 218 | ) -> Program: 219 | prgm = Program() 220 | 221 | for qubit in qubits: 222 | prgm += H(qubit) 223 | 224 | # IBM QisKit http://tiny.cc/wn7uaz 225 | #number_of_iters : Callable[[int], int] = \ 226 | # lambda size: int(sqrt(2 ** size)) 227 | 228 | # arxiv http://tiny.cc/mo7uaz 229 | number_of_iters : Callable[[int], int] = \ 230 | lambda size: int(pi / 4 / asin(sqrt(1 / (2 ** size)))) 231 | 232 | for _ in range(number_of_iters(len(qubits))): 233 | prgm += If(predicate).Flip() 234 | 235 | for qubit in qubits: 236 | prgm += H(qubit) 237 | 238 | prgm += If(Zero(qubits)).Flip() 239 | 240 | for qubit in qubits: 241 | prgm += H(qubit) 242 | 243 | prgm += Flip(qubits) 244 | 245 | return prgm 246 | 247 | prgm = Program() 248 | qubits = prgm.Qubits([0, 0, 0]) 249 | prgm += Grover(qubits, predicate=Match(qubits, mask=[0, 1, 0])) 250 | print(Quasar().to_qasm_str(prgm)) 251 | 252 | example_grover() 253 | ``` 254 | 255 | ```python 256 | from typing import List 257 | from math import pi 258 | 259 | from quasar import All, CNot, If, H, Phase, Program, Quasar, Qubit 260 | 261 | def example_fourier() -> None: 262 | def Fourier( 263 | qubits: List[Qubit] 264 | ) -> Program: 265 | prgm = Program() 266 | 267 | length = len(qubits) 268 | 269 | for i in range(length): 270 | prgm += H(qubits[i]) 271 | 272 | for j in range(2, length + 1 - i): 273 | prgm += If(All(qubits[i + j - 1])).Then( 274 | Phase(qubits[i], 2 * pi / (2 ** j)) 275 | ) 276 | 277 | for i in range(length // 2): 278 | prgm += CNot(qubits[i], qubits[length - i - 1]) 279 | prgm += CNot(qubits[length - i - 1], qubits[i]) 280 | prgm += CNot(qubits[i], qubits[length - i - 1]) 281 | 282 | return prgm 283 | 284 | prgm = Program() 285 | qubits = prgm.Qubits([0, 1]) 286 | prgm += Fourier(qubits) 287 | print(Quasar().to_qasm_str(prgm)) 288 | 289 | example_fourier() 290 | ``` 291 | 292 | # License 293 | 294 | MIT 295 | 296 | [IBM Qiskit]: https://qiskit.org/ 297 | [Google Cirq]: https://github.com/quantumlib/Cirq 298 | [OpenQASM]: https://github.com/QISKit/openqasm 299 | [U3]: https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/extensions/standard/u3.py 300 | -------------------------------------------------------------------------------- /builtin_arithmetics.py: -------------------------------------------------------------------------------- 1 | from cmath import exp, phase 2 | from math import cos, sin, acos 3 | 4 | from typing import List, Tuple 5 | 6 | from builtin_gates import BuiltinGate, X_GATE, Y_GATE, Z_GATE, H_GATE, U3_GATE 7 | from quasar_cmd import GateCmd 8 | 9 | 10 | def invert_gate(gate: BuiltinGate, params: List[float]) -> Tuple[BuiltinGate, List[float]]: 11 | if gate in {X_GATE, Y_GATE, Z_GATE, H_GATE}: 12 | assert not params 13 | return gate, params 14 | if gate == U3_GATE: 15 | return U3_GATE, [ 16 | -params[0], # theta -> -theta 17 | -params[2], # phi -> -lambda 18 | -params[1], # lambda -> -phi 19 | ] 20 | raise NotImplementedError(f'Dont know how to invert {gate} {params}') 21 | 22 | 23 | def check_commutation(cmd1: GateCmd, cmd2: GateCmd) -> bool: 24 | """ Returns True if two gates can be swapped with no change to the outcome. """ 25 | # TODO(adsz): In future, it might be also beneficial to check commutation 26 | # that alters the gates (with similar gate complexity). 27 | 28 | if cmd1 == cmd2: 29 | return True 30 | if not ((cmd1.get_control_qubit_ids() | {cmd1.get_target_qubit_id()}) & 31 | (cmd2.get_control_qubit_ids() | {cmd2.get_target_qubit_id()})): 32 | return True 33 | if cmd1.gate == Z_GATE and cmd2.gate == Z_GATE: 34 | return True 35 | if cmd1.gate == X_GATE and cmd2.gate == X_GATE: 36 | if cmd1.get_target_qubit_id() in cmd2.get_control_qubit_ids(): 37 | return False 38 | elif cmd2.get_target_qubit_id() in cmd1.get_control_qubit_ids(): 39 | return False 40 | else: 41 | return True 42 | # TODO(adsz): Two simplest rules as a starting point. Add more rules. 43 | 44 | return False 45 | 46 | 47 | def is_zero(num: complex) -> bool: 48 | return abs(num) < 1e-9 49 | 50 | 51 | def reduce_consecutive_u3(a: float, b: float, c: float, x: float, y: float, z: float) \ 52 | -> Tuple[float, float, float, float]: 53 | """ Returns simplification parameters for two consecutive U3 gates. Given equation of a form: 54 | U3(a, b, c) * U3(x, y, z) = exp(1j * phi) * U3(alpha, beta, gamma), 55 | for given a, b, c, x, y, z - real numbers, function returns real phi, alpha, beta and gamma.""" 56 | 57 | expcy = exp(1j * (c + y)) 58 | 59 | s_sum = sin((a+x) / 2) * (1 + expcy) / 2 60 | s_sub = sin((a-x) / 2) * (1 - expcy) / 2 61 | c_sum = cos((a+x) / 2) * (1 + expcy) / 2 62 | c_sub = cos((a-x) / 2) * (1 - expcy) / 2 63 | 64 | elem1 = c_sum + c_sub 65 | elem2 = s_sum - s_sub 66 | elem3 = s_sum + s_sub 67 | elem4 = c_sum - c_sub 68 | 69 | phi = phase(elem1) 70 | alpha = 2 * acos(abs(elem1)) 71 | if is_zero(elem2): # any solution having beta + gamma = phase(elem4) - phi 72 | beta = 0. 73 | gamma = phase(elem4) + b + z - phi 74 | else: 75 | beta = phase(elem3) + b - phi 76 | gamma = phase(elem2) + z - phi 77 | 78 | return phi, alpha, beta, gamma 79 | -------------------------------------------------------------------------------- /builtin_arithmetics_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from cmath import exp, cos, sin, pi 3 | from itertools import product 4 | 5 | import numpy as np 6 | from numpy.testing import assert_allclose 7 | from random import random, seed 8 | 9 | from builtin_arithmetics import invert_gate, reduce_consecutive_u3 10 | from builtin_gates import X_GATE, Y_GATE, Z_GATE, H_GATE, U3_GATE 11 | 12 | 13 | def _u3(a: float, b: float, c: float) -> np.array: 14 | return np.array([ 15 | [cos(a / 2), -exp(c * 1j) * sin(a / 2)], 16 | [exp(b * 1j) * sin(a / 2), exp((b + c) * 1j) * cos(a / 2)], 17 | ]) 18 | 19 | 20 | def _rand_ang() -> float: 21 | return random() * 4 * pi 22 | 23 | 24 | class BuiltinArithmeticsTest(unittest.TestCase): 25 | 26 | def test_inverse_x(self) -> None: 27 | self.assertEqual( 28 | invert_gate(X_GATE, []), 29 | (X_GATE, [])) 30 | 31 | def test_inverse_y(self) -> None: 32 | self.assertEqual( 33 | invert_gate(Y_GATE, []), 34 | (Y_GATE, [])) 35 | 36 | def test_inverse_z(self) -> None: 37 | self.assertEqual( 38 | invert_gate(Z_GATE, []), 39 | (Z_GATE, [])) 40 | 41 | def test_inverse_h(self) -> None: 42 | self.assertEqual( 43 | invert_gate(H_GATE, []), 44 | (H_GATE, [])) 45 | 46 | def test_inverse_u3(self) -> None: 47 | self.assertEqual( 48 | invert_gate(U3_GATE, [0, 0, 0]), 49 | (U3_GATE, [0, 0, 0])) 50 | self.assertEqual( 51 | invert_gate(U3_GATE, [2.2, 3.3, 4.4]), 52 | (U3_GATE, [-2.2, -4.4, -3.3])) 53 | 54 | def test_conversion_random_angles(self) -> None: 55 | seed(7777) 56 | for test_id in range(20): 57 | a = _rand_ang() 58 | b = _rand_ang() 59 | c = _rand_ang() 60 | x = _rand_ang() 61 | y = _rand_ang() 62 | z = _rand_ang() 63 | self._test_conversion(a, b, c, x, y, z) 64 | 65 | def test_conversion_fixed_angles(self) -> None: 66 | angles_first = [0, pi / 4, pi / 2] 67 | angles_second = [0, pi / 2] 68 | for a, b, c in product(angles_first, repeat=3): 69 | for x, y, z in product(angles_second, repeat=3): 70 | self._test_conversion(a, b, c, x, y, z) 71 | 72 | def _test_conversion(self, a: float, b: float, c: float, x: float, y: float, z: float) -> None: 73 | u3_1 = _u3(a, b, c) 74 | u3_2 = _u3(x, y, z) 75 | expected = u3_1 @ u3_2 76 | conv_args = reduce_consecutive_u3(a, b, c, x, y, z) 77 | 78 | conv_matrix = exp(1j * conv_args[0]) * _u3(conv_args[1], conv_args[2], conv_args[3]) 79 | assert_allclose(abs(conv_matrix - expected), 0, atol=1e-7) 80 | 81 | 82 | if __name__ == '__main__': 83 | unittest.main() 84 | -------------------------------------------------------------------------------- /builtin_gates.py: -------------------------------------------------------------------------------- 1 | """ This file defines the only gates that are supported by 2 | the QCompiler natively. This list should be kept minimal, as adding new gate 3 | would require implementing its behavior and interaction with others. 4 | In fact, sole U3 should be enough, however others like X, Z are added for easier use. 5 | Whenever handling the builtin gate, prefer referring to the `BuiltinGate` object 6 | instead of its name -- to emphasize its builtinness. 7 | All control gates (by any number of qubits) are builtins implicitly.""" 8 | 9 | from dataclasses import dataclass 10 | 11 | 12 | @dataclass(frozen=True) 13 | class BuiltinGate: 14 | name: str 15 | num_params: int 16 | 17 | 18 | X_GATE = BuiltinGate('X', 0) 19 | Y_GATE = BuiltinGate('Y', 0) 20 | Z_GATE = BuiltinGate('Z', 0) 21 | H_GATE = BuiltinGate('H', 0) 22 | U3_GATE = BuiltinGate('U3', 3) 23 | 24 | def builtin_repr(gate: BuiltinGate) -> str: 25 | return f'{gate.name}_GATE' 26 | -------------------------------------------------------------------------------- /qfourier.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from math import pi 24 | from typing import List 25 | 26 | from quasar import All, H, If, Phase, Program, Qubit, Swap 27 | 28 | # 29 | ## 30 | # 31 | 32 | def Fourier( 33 | qubits: List[Qubit] 34 | ) -> Program: 35 | prgm = Program() 36 | 37 | length = len(qubits) 38 | 39 | for i in range(length): 40 | prgm += H(qubits[i]) 41 | 42 | for j in range(2, length + 1 - i): 43 | prgm += If(All(qubits[i + j - 1])).Then( 44 | Phase(qubits[i], 2 * pi / (2 ** j)) 45 | ) 46 | 47 | for i in range(length // 2): 48 | prgm += Swap(qubits[i], qubits[length - i - 1]) 49 | 50 | return prgm 51 | -------------------------------------------------------------------------------- /qfourier_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import Dict, Tuple 24 | 25 | import unittest 26 | 27 | from quasar import Program, Quasar 28 | from qfourier import Fourier 29 | 30 | # 31 | ## 32 | # 33 | 34 | class FourierTest(unittest.TestCase): 35 | 36 | def test_fourier(self) -> None: 37 | prgm = Program() 38 | qubits = prgm.Qubits([0, 0, 0, 0]) 39 | prgm += Fourier(qubits) 40 | 41 | actual = Quasar().to_qasm_str(prgm) 42 | expected = ( 43 | 'OPENQASM 2.0;' + '\n' + 44 | 'include "qelib1.inc";' + '\n' + 45 | ' ' + '\n' + 46 | 'qreg q[4];' + '\n' + 47 | 'creg c[0];' + '\n' + 48 | ' ' + '\n' + 49 | 'h q[0];' + '\n' + 50 | 'cu3(0, 0, 1.5707963267948966) q[1], q[0];' + '\n' + 51 | 'cu3(0, 0, 0.7853981633974483) q[2], q[0];' + '\n' + 52 | 'cu3(0, 0, 0.39269908169872414) q[3], q[0];' + '\n' + 53 | 'h q[1];' + '\n' + 54 | 'cu3(0, 0, 1.5707963267948966) q[2], q[1];' + '\n' + 55 | 'cu3(0, 0, 0.7853981633974483) q[3], q[1];' + '\n' + 56 | 'h q[2];' + '\n' + 57 | 'cu3(0, 0, 1.5707963267948966) q[3], q[2];' + '\n' + 58 | 'h q[3];' + '\n' + 59 | 'cx q[0], q[3];' + '\n' + 60 | 'cx q[3], q[0];' + '\n' + 61 | 'cx q[0], q[3];' + '\n' + 62 | 'cx q[1], q[2];' + '\n' + 63 | 'cx q[2], q[1];' + '\n' + 64 | 'cx q[1], q[2];' 65 | ) 66 | 67 | self.assertEqual(actual, expected) 68 | 69 | 70 | if __name__ == '__main__': 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /qgrover.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from math import asin, pi, sqrt 24 | from typing import Callable, List 25 | 26 | from quasar import ASTNode, H, Flip, If, Program, Qubit, Zero 27 | 28 | # 29 | ## 30 | # 31 | 32 | def Grover( 33 | qubits: List[Qubit], 34 | predicate: ASTNode 35 | ) -> Program: 36 | prgm = Program() 37 | 38 | for qubit in qubits: 39 | prgm += H(qubit) 40 | 41 | # IBM QisKit http://tiny.cc/wn7uaz 42 | #number_of_iters : Callable[[int], int] = \ 43 | # lambda size: int(sqrt(2 ** size)) 44 | 45 | #arxiv http://tiny.cc/mo7uaz 46 | number_of_iters : Callable[[int], int] = \ 47 | lambda size: int(pi / 4 / asin(sqrt(1 / (2 ** size)))) 48 | 49 | for _ in range(number_of_iters(len(qubits))): 50 | prgm += If(predicate).Flip() 51 | 52 | for qubit in qubits: 53 | prgm += H(qubit) 54 | 55 | prgm += If(Zero(qubits)).Flip() 56 | 57 | for qubit in qubits: 58 | prgm += H(qubit) 59 | 60 | prgm += Flip(qubits) 61 | 62 | return prgm 63 | -------------------------------------------------------------------------------- /qgrover_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import Dict, List, Tuple 24 | 25 | import unittest 26 | 27 | from quasar import Quasar, Match, Program 28 | from qgrover import Grover 29 | 30 | # 31 | ## 32 | # 33 | 34 | class GroverTest(unittest.TestCase): 35 | 36 | def test_grover(self) -> None: 37 | input_mask = (1, 0, 1) 38 | prgm = Program() 39 | qubits = prgm.Qubits([0, 0, 0]) 40 | prgm += Grover(qubits, predicate=Match(qubits, mask=input_mask)) 41 | 42 | actual = Quasar().to_qasm_str(prgm) 43 | expected = ( 44 | 'OPENQASM 2.0;' + '\n' 45 | 'include "qelib1.inc";' + '\n' 46 | ' ' + '\n' 47 | 'qreg q[4];' + '\n' 48 | 'creg c[0];' + '\n' 49 | ' ' + '\n' 50 | 'h q[0];' + '\n' 51 | 'h q[1];' + '\n' 52 | 'h q[2];' + '\n' 53 | 'x q[1];' + '\n' 54 | 'ccx q[0], q[1], q[3];' + '\n' 55 | 'cz q[2], q[3];' + '\n' 56 | 'ccx q[0], q[1], q[3];' + '\n' 57 | 'x q[1];' + '\n' 58 | 'h q[0];' + '\n' 59 | 'h q[1];' + '\n' 60 | 'h q[2];' + '\n' 61 | 'x q[0];' + '\n' 62 | 'x q[1];' + '\n' 63 | 'x q[2];' + '\n' 64 | 'ccx q[0], q[1], q[3];' + '\n' 65 | 'cz q[2], q[3];' + '\n' 66 | 'ccx q[0], q[1], q[3];' + '\n' 67 | 'x q[2];' + '\n' 68 | 'x q[1];' + '\n' 69 | 'x q[0];' + '\n' 70 | 'h q[0];' + '\n' 71 | 'h q[1];' + '\n' 72 | 'h q[2];' + '\n' 73 | 'z q[0];' + '\n' 74 | 'x q[0];' + '\n' 75 | 'z q[0];' + '\n' 76 | 'x q[0];' + '\n' 77 | 'x q[1];' + '\n' 78 | 'ccx q[0], q[1], q[3];' + '\n' 79 | 'cz q[2], q[3];' + '\n' 80 | 'ccx q[0], q[1], q[3];' + '\n' 81 | 'x q[1];' + '\n' 82 | 'h q[0];' + '\n' 83 | 'h q[1];' + '\n' 84 | 'h q[2];' + '\n' 85 | 'x q[0];' + '\n' 86 | 'x q[1];' + '\n' 87 | 'x q[2];' + '\n' 88 | 'ccx q[0], q[1], q[3];' + '\n' 89 | 'cz q[2], q[3];' + '\n' 90 | 'ccx q[0], q[1], q[3];' + '\n' 91 | 'x q[2];' + '\n' 92 | 'x q[1];' + '\n' 93 | 'x q[0];' + '\n' 94 | 'h q[0];' + '\n' 95 | 'h q[1];' + '\n' 96 | 'h q[2];' + '\n' 97 | 'z q[0];' + '\n' 98 | 'x q[0];' + '\n' 99 | 'z q[0];' + '\n' 100 | 'x q[0];' 101 | ) 102 | 103 | self.assertEqual(actual, expected) 104 | 105 | 106 | if __name__ == '__main__': 107 | unittest.main() 108 | -------------------------------------------------------------------------------- /quasar.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from math import pi 24 | from typing import List, Union 25 | 26 | from builtin_gates import U3_GATE, X_GATE, Y_GATE, Z_GATE, H_GATE 27 | from quasar_ast import Program, ProgramLike, GateNode, IASTNode, IfNode, MatchNode, NotNode, MeasurementNode, ResetNode, QubitNode, InvNode 28 | from quasar_cmd import ICommand 29 | from quasar_comp import CompileVisitor, ResourceAllocator, to_list 30 | from quasar_formatter import IQAsmFormatter 31 | from quasar_opt import QuasarOpt 32 | from quasar_qasm import QASMFormatter 33 | 34 | # 35 | ## 36 | # 37 | 38 | class Quasar: 39 | def _commands_to_code( 40 | self, 41 | commands: List[ICommand], 42 | qasm_formatter: IQAsmFormatter 43 | ) -> List[str]: 44 | code : List[str] = [] 45 | 46 | for command in commands: 47 | for line in command.get_lines(qasm_formatter): 48 | code.append(line) 49 | 50 | return code 51 | 52 | def to_qasm_str( 53 | self, 54 | root: ProgramLike, 55 | optimize: bool = True 56 | ) -> str: 57 | return '\n'.join(Quasar().compile(root, QASMFormatter(), optimize)) 58 | 59 | def compile( 60 | self, 61 | root: ProgramLike, 62 | qasm_formatter, 63 | optimize: bool = True 64 | ) -> List[str]: 65 | root = Program(root) 66 | rsrc = ResourceAllocator() 67 | compile_visitor = CompileVisitor(rsrc) 68 | root.accept(compile_visitor) 69 | 70 | max_used_qubit_id = compile_visitor.get_max_used_qubit_id() 71 | max_used_bit_id = compile_visitor.get_max_used_bit_id() 72 | 73 | commands: List[ICommand] = compile_visitor.commands 74 | 75 | if optimize: 76 | commands = QuasarOpt.run(commands, max_used_qubit_id) 77 | 78 | qasm_formatter.set_qubits_counter(max_used_qubit_id) 79 | qasm_formatter.set_bits_counter(max_used_bit_id) 80 | qasm_formatter.set_groups([max_used_qubit_id]) 81 | 82 | headers = qasm_formatter.get_headers() 83 | 84 | code = self._commands_to_code( 85 | commands, 86 | qasm_formatter, 87 | ) 88 | 89 | footers = qasm_formatter.get_footers() 90 | 91 | return headers + code + footers 92 | 93 | # 94 | ## 95 | # 96 | 97 | ASTNode = IASTNode 98 | 99 | Not = NotNode 100 | 101 | def All(qubits: Union[QubitNode, List[QubitNode]]) -> MatchNode: 102 | qubits = to_list(qubits) 103 | return MatchNode(qubits, [1]*len(qubits)) 104 | 105 | def Zero(qubits: Union[QubitNode, List[QubitNode]]) -> MatchNode: 106 | qubits = to_list(qubits) 107 | return MatchNode(qubits, [0]*len(qubits)) 108 | 109 | def Any(controls: Union[QubitNode, List[QubitNode]]) -> NotNode: 110 | return Not(Zero(controls)) 111 | 112 | def U1(target_qubit: QubitNode, arg1: float) -> IASTNode: 113 | return GateNode(U3_GATE, target_qubit, params=[0, 0, arg1]) 114 | 115 | def CU1(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float) -> IASTNode: 116 | return IfNode(All(control_qubit)).Then(U1(target_qubit, arg1)) 117 | 118 | def U2(target_qubit: QubitNode, arg1: float, arg2: float) -> IASTNode: 119 | return GateNode(U3_GATE, target_qubit, params=[pi/2, arg1, arg2]) 120 | 121 | def CU2(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float, arg2: float) -> IASTNode: 122 | return IfNode(All(control_qubit)).Then(U2(target_qubit, arg1, arg2)) 123 | 124 | def U3(target_qubit: QubitNode, arg1: float, arg2: float, arg3: float) -> IASTNode: 125 | return GateNode(U3_GATE, target_qubit, params=[arg1, arg2, arg3]) 126 | 127 | def CU3(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float, arg2: float, arg3: float) -> IASTNode: 128 | return IfNode(All(control_qubit)).Then(U3(target_qubit, arg1, arg2, arg3)) 129 | 130 | def CX(control_qubit: QubitNode, target_qubit: QubitNode) -> IASTNode: 131 | return IfNode(All(control_qubit)).Then(GateNode(X_GATE, target_qubit)) 132 | 133 | CNot = CX 134 | 135 | def CCX(control_qubit_1: QubitNode, control_qubit_2: QubitNode, target_qubit: QubitNode) -> IASTNode: 136 | return IfNode(All([control_qubit_1, control_qubit_2])).Then(GateNode(X_GATE, target_qubit)) 137 | 138 | # Unconditional Flip <=> If(True).Flip() <=> For_Each_State().Flip() 139 | # With these two statements 140 | # If( ( condition ) ).Then( code ) 141 | # If( not( condition ) ).Then( code ) 142 | # code is always executed. 143 | def Flip(qubits: List[QubitNode]) -> List[IASTNode]: 144 | return [ 145 | IfNode( ( All(qubits[0]) ) ).Flip(), 146 | IfNode( NotNode( All(qubits[0]) ) ).Flip() 147 | ] 148 | 149 | If = IfNode 150 | 151 | Inv = InvNode 152 | 153 | Match = MatchNode 154 | 155 | Measurement = MeasurementNode 156 | 157 | Reset = ResetNode 158 | 159 | def Phase(target_qubit: QubitNode, arg1: float) -> IASTNode: 160 | return U1(target_qubit, arg1) 161 | 162 | def Id(target_qubit: QubitNode) -> IASTNode: 163 | return U1(target_qubit, 0) 164 | 165 | Qubit = QubitNode 166 | 167 | def RX(target_qubit: QubitNode, arg1: float) -> IASTNode: 168 | return U3(target_qubit, arg1, -pi/2, pi/2) 169 | 170 | def CRX(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float) -> IASTNode: 171 | return IfNode(All(control_qubit)).Then(RX(target_qubit, arg1)) 172 | 173 | def RY(target_qubit: QubitNode, arg1: float) -> IASTNode: 174 | return U3(target_qubit, arg1, 0, 0) 175 | 176 | def CRY(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float) -> IASTNode: 177 | return IfNode(All(control_qubit)).Then(RY(target_qubit, arg1)) 178 | 179 | def RZ(target_qubit: QubitNode, arg1: float) -> Program: 180 | return Program([ 181 | Phase(target_qubit, arg1/2), 182 | X(target_qubit), 183 | Phase(target_qubit, -arg1/2), 184 | X(target_qubit) 185 | ]) 186 | 187 | def CRZ(control_qubit: QubitNode, target_qubit: QubitNode, arg1: float) -> IASTNode: 188 | return IfNode(All(control_qubit)).Then(RZ(target_qubit, arg1)) 189 | 190 | def CZ(control_qubit: QubitNode, target_qubit: QubitNode) -> IASTNode: 191 | # CZ(c, t) != CRZ(c, t, pi/2) 192 | return IfNode(All(control_qubit)).Then(Z(target_qubit)) 193 | 194 | def CCZ(control_qubit_1: QubitNode, control_qubit_2: QubitNode, target_qubit: QubitNode) -> Program: 195 | return Program([ 196 | H(target_qubit), 197 | CCX(control_qubit_1, control_qubit_2, target_qubit), 198 | H(target_qubit) 199 | ]) 200 | 201 | def S(target_qubit: QubitNode) -> IASTNode: 202 | return Phase(target_qubit, pi/2) 203 | 204 | def Sdg(target_qubit: QubitNode) -> IASTNode: 205 | return Phase(target_qubit, -pi/2) 206 | 207 | def T(target_qubit: QubitNode) -> IASTNode: 208 | return Phase(target_qubit, pi/4) 209 | 210 | def Tdg(target_qubit: QubitNode) -> IASTNode: 211 | return Phase(target_qubit, -pi/4) 212 | 213 | def Seq(*nodes) -> Program: 214 | return Program(list(nodes)) 215 | 216 | def Swap(q1: QubitNode, q2: QubitNode) -> Program: 217 | return Seq( 218 | CX(q1, q2), 219 | CX(q2, q1), 220 | CX(q1, q2) 221 | ) 222 | 223 | def X(target_qubit: QubitNode) -> GateNode: 224 | return GateNode(X_GATE, target_qubit) 225 | 226 | def Y(target_qubit: QubitNode) -> GateNode: 227 | return GateNode(Y_GATE, target_qubit) 228 | 229 | def Z(target_qubit: QubitNode) -> GateNode: 230 | return GateNode(Z_GATE, target_qubit) 231 | 232 | def H(target_qubit: QubitNode) -> GateNode: 233 | return GateNode(H_GATE, target_qubit) 234 | -------------------------------------------------------------------------------- /quasar_ast.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from abc import abstractmethod, ABC 24 | from typing import Iterable, List, Optional, TypeVar, Union 25 | 26 | from builtin_gates import BuiltinGate, X_GATE 27 | 28 | # 29 | ## 30 | # 31 | 32 | T = TypeVar('T') 33 | 34 | def to_list(obj: Union[T, List[T]]) -> List[T]: 35 | if isinstance(obj, list): 36 | return obj 37 | else: 38 | return [obj] 39 | 40 | # 41 | ## 42 | # 43 | 44 | class IASTVisitable(ABC): 45 | @abstractmethod 46 | def accept(self, visitor: 'IASTVisitor') -> Optional['IASTNode']: 47 | pass 48 | 49 | # 50 | ## 51 | # 52 | 53 | class IASTNode(IASTVisitable): 54 | def __init__(self) -> None: 55 | self._target_qubit_id : int = -1 56 | 57 | self._control_positive_qubit_ids : List[int] = [] 58 | self._control_negative_qubit_ids : List[int] = [] 59 | 60 | def __add__(self, other: Union['IASTNode', List['IASTNode'], 'Program']) -> 'Program': 61 | if (isinstance(other, IASTNode)): 62 | return Program([self] + [other]) 63 | 64 | elif (isinstance(other, list)): 65 | return Program([self] + other) 66 | 67 | elif (isinstance(other, Program)): 68 | return Program([self] + other._nodes) 69 | 70 | raise TypeError(f'Cannot construct Program from type {type(other)}') 71 | 72 | def set_target_qubit_id(self, target_qubit_id: int) -> None: 73 | self._target_qubit_id = target_qubit_id 74 | 75 | def get_target_qubit_id(self) -> int: 76 | return self._target_qubit_id 77 | 78 | 79 | ProgramLike = Union[IASTNode, List[IASTNode], 'Program'] 80 | 81 | class Program(IASTVisitable): 82 | _qubit_counter = 0 83 | _cbit_counter = 0 84 | 85 | def __init__(self, other: Optional[ProgramLike] = None) -> None: 86 | self._nodes : List[IASTNode] = [] 87 | 88 | if isinstance(other, Program): 89 | self._nodes = other._nodes or [] 90 | 91 | elif isinstance(other, IASTNode): 92 | self._nodes = [other] 93 | 94 | elif isinstance(other, list): 95 | self._nodes = other or [] 96 | 97 | elif other is not None: 98 | raise Exception(f'Unknown type {type(other)}') 99 | 100 | def __add__(self, other: ProgramLike) -> 'Program': 101 | return Program(self._nodes + Program(other)._nodes) 102 | 103 | def __iadd__(self, other: ProgramLike) -> 'Program': 104 | self._nodes.extend(Program(other)._nodes) 105 | return self 106 | 107 | def __getitem__(self, index: int) -> IASTNode: 108 | return self._nodes[index] 109 | 110 | def __len__(self) -> int: 111 | return len(self._nodes) 112 | 113 | def accept(self, visitor: 'IASTVisitor') -> None: 114 | visitor.on_program(self) 115 | 116 | def Qubit(self, init=0) -> 'QubitNode': 117 | qubit = QubitNode() 118 | qubit.set_name(f'$$_qubit_{Program._qubit_counter}') 119 | Program._qubit_counter += 1 120 | 121 | self._nodes.append(QubitDeclarationNode(qubit)) 122 | 123 | if init == 1: 124 | self._nodes.append(GateNode(X_GATE, qubit)) 125 | 126 | return qubit 127 | 128 | def Qubits(self, inits: Iterable[int]) -> List['QubitNode']: 129 | return [self.Qubit(init) for init in inits] 130 | 131 | def CBit(self) -> 'CBitNode': 132 | cbit = CBitNode() 133 | cbit.set_name(f'$$_cbit_{Program._cbit_counter}') 134 | Program._cbit_counter += 1 135 | self._nodes.append(cbit) 136 | return cbit 137 | 138 | def CBits(self, size: int) -> List['CBitNode']: 139 | return [self.CBit() for i in range(size)] 140 | 141 | 142 | class QubitNode(IASTNode): 143 | def __init__(self, id_=-1) -> None: 144 | super().__init__() 145 | super().set_target_qubit_id(id_) 146 | 147 | def set_name(self, name: str) -> None: 148 | self._name = name 149 | 150 | def get_name(self) -> str: 151 | return self._name 152 | 153 | def get_id(self) -> int: 154 | return super().get_target_qubit_id() 155 | 156 | def accept(self, visitor: 'IASTVisitor') -> None: 157 | visitor.on_qubit(self) 158 | 159 | 160 | class QubitDeclarationNode(IASTNode): 161 | def __init__(self, qubit: QubitNode) -> None: 162 | self._qubit = qubit 163 | 164 | def get_qubit(self) -> QubitNode: 165 | return self._qubit 166 | 167 | def accept(self, visitor: 'IASTVisitor') -> None: 168 | visitor.on_qubit_declaraion(self) 169 | 170 | 171 | class CBitNode(IASTNode): 172 | 173 | def __init__(self, target_bit_id=-1) -> None: 174 | super().__init__() 175 | self._target_bit_id = target_bit_id 176 | 177 | def set_name(self, name: str) -> None: 178 | self._name = name 179 | 180 | def get_name(self) -> str: 181 | return self._name 182 | 183 | def set_id(self, target_bit_id: int) -> None: 184 | self._target_bit_id = target_bit_id 185 | 186 | def get_id(self) -> int: 187 | return self._target_bit_id 188 | 189 | def accept(self, visitor: 'IASTVisitor') -> None: 190 | visitor.on_cvar(self) 191 | 192 | 193 | class InvNode(IASTNode): 194 | def __init__(self, node: ProgramLike) -> None: 195 | super().__init__() 196 | self._body : Program = Program(node) 197 | 198 | def get_body(self) -> Program: 199 | return self._body 200 | 201 | def accept(self, visitor: 'IASTVisitor') -> None: 202 | visitor.on_inv(self) 203 | 204 | 205 | class ConditionNode(IASTNode): 206 | pass 207 | 208 | 209 | class IfASTNode(IASTNode): 210 | def __init__(self, condition: ConditionNode, then_body: ProgramLike) -> None: 211 | super().__init__() 212 | self._condition = condition 213 | self._then_body = Program(then_body) 214 | 215 | def get_condition(self) -> ConditionNode: 216 | return self._condition 217 | 218 | def get_then_body(self) -> Program: 219 | return self._then_body 220 | 221 | 222 | class IfThenElseNode(IfASTNode): 223 | def __init__(self, condition: ConditionNode, then_body: ProgramLike, else_body: ProgramLike) -> None: 224 | super().__init__(condition, then_body) 225 | self._else_body = Program(else_body) 226 | 227 | def get_else_body(self) -> Program: 228 | return self._else_body 229 | 230 | def set_control_positive_qubit_ids(self, control_positive_qubit_ids: List[int]) -> None: 231 | self._control_positive_qubit_ids = control_positive_qubit_ids 232 | 233 | def get_control_positive_qubit_ids(self) -> List[int]: 234 | return self._control_positive_qubit_ids 235 | 236 | def _set_control_negative_qubit_ids(self, control_negative_qubit_ids: List[int]) -> None: 237 | self._control_negative_qubit_ids = control_negative_qubit_ids 238 | 239 | def get_control_negative_qubit_ids(self) -> List[int]: 240 | return self._control_negative_qubit_ids 241 | 242 | def accept(self, visitor: 'IASTVisitor') -> None: 243 | visitor.on_if_then_else(self) 244 | 245 | 246 | class IfThenNode(IfASTNode): 247 | def __init__(self, condition: ConditionNode, then_body: ProgramLike) -> None: 248 | super().__init__(condition, then_body) 249 | 250 | def Else(self, else_body: ProgramLike) -> IfThenElseNode: 251 | return IfThenElseNode(self._condition, self._then_body, else_body) 252 | 253 | def set_control_positive_qubit_ids(self, control_positive_qubit_ids: List[int]) -> None: 254 | self._control_positive_qubit_ids = control_positive_qubit_ids 255 | 256 | def get_control_positive_qubit_ids(self) -> List[int]: 257 | return self._control_positive_qubit_ids 258 | 259 | def accept(self, visitor: 'IASTVisitor') -> None: 260 | visitor.on_if_then(self) 261 | 262 | 263 | class IfFlipNode(IASTNode): 264 | def __init__(self, condition: ConditionNode) -> None: 265 | super().__init__() 266 | self._condition = condition 267 | 268 | def get_condition(self) -> ConditionNode: 269 | return self._condition 270 | 271 | def set_control_positive_qubit_ids(self, control_positive_qubit_ids: List[int]) -> None: 272 | self._control_positive_qubit_ids = control_positive_qubit_ids 273 | 274 | def get_control_positive_qubit_ids(self) -> List[int]: 275 | return self._control_positive_qubit_ids 276 | 277 | def accept(self, visitor: 'IASTVisitor') -> None: 278 | visitor.on_if_flip(self) 279 | 280 | 281 | class IfNode(IASTNode): 282 | def __init__(self, condition: ConditionNode) -> None: 283 | super().__init__() 284 | self._condition = condition 285 | 286 | def Then(self, then_body: ProgramLike) -> IfThenNode: 287 | return IfThenNode(self._condition, then_body) 288 | 289 | def Flip(self) -> IfFlipNode: 290 | return IfFlipNode(self._condition) 291 | 292 | def get_control_negative_qubit_ids(self) -> List[int]: 293 | raise NotImplementedError() 294 | 295 | def accept(self, visitor: 'IASTVisitor') -> None: 296 | raise NotImplementedError() 297 | 298 | 299 | class GateNode(IASTNode): 300 | """ This node represents an application of a builtin gate on a specified qubit. """ 301 | 302 | def __init__(self, gate: BuiltinGate, qubit: QubitNode, params: List[float] = None) -> None: 303 | super().__init__() 304 | self._gate = gate 305 | self._params = params or [] 306 | self._target_qubit = qubit 307 | assert len(self.params) == gate.num_params 308 | 309 | @property 310 | def gate(self) -> BuiltinGate: 311 | return self._gate 312 | 313 | @property 314 | def params(self) -> List[float]: 315 | return self._params 316 | 317 | def get_target_qubit(self) -> QubitNode: 318 | return self._target_qubit 319 | 320 | def get_target_qubit_id(self) -> int: 321 | return self._target_qubit.get_target_qubit_id() 322 | 323 | def accept(self, visitor: 'IASTVisitor') -> None: 324 | visitor.on_gate(self) 325 | 326 | 327 | class MatchNode(ConditionNode): 328 | def __init__(self, control_qubits: Union[QubitNode, List[QubitNode]], mask: List[int]) -> None: 329 | super().__init__() 330 | self._control_qubits = to_list(control_qubits) 331 | self._mask = mask 332 | self._control_positive_qubit_ids : List[int] = [] 333 | self._control_negative_qubit_ids : List[int] = [] 334 | 335 | if (len(self.get_control_qubits()) != len(self.get_mask())): 336 | raise Exception( 337 | 'Inside MATCH statement: Var ' + 338 | ' should have the same size as a mask length' 339 | ) 340 | 341 | def get_mask(self) -> List[int]: 342 | return self._mask 343 | 344 | def get_control_qubits(self) -> List[QubitNode]: 345 | return self._control_qubits 346 | 347 | def accept(self, visitor: 'IASTVisitor') -> None: 348 | visitor.on_match(self) 349 | 350 | 351 | class NotNode(ConditionNode): 352 | def __init__(self, condition: ConditionNode) -> None: 353 | super().__init__() 354 | self._condition = condition 355 | 356 | def get_condition(self) -> ConditionNode: 357 | return self._condition 358 | 359 | def set_target_qubit_id(self, target_qubit_id: int): 360 | self._condition.set_target_qubit_id(target_qubit_id) 361 | 362 | def get_target_qubit_id(self) -> int: 363 | return self._condition.get_target_qubit_id() 364 | 365 | def accept(self, visitor: 'IASTVisitor') -> None: 366 | visitor.on_not(self) 367 | 368 | 369 | class MeasurementNode(IASTNode): 370 | def __init__(self, qubit: QubitNode, bit: CBitNode) -> None: 371 | super().__init__() 372 | self._qubit = qubit 373 | self._bit = bit 374 | 375 | def get_qubit(self) -> QubitNode: 376 | return self._qubit 377 | 378 | def get_bit(self) -> CBitNode: 379 | return self._bit 380 | 381 | def accept(self, visitor: 'IASTVisitor') -> None: 382 | visitor.on_measure(self) 383 | 384 | 385 | class ResetNode(IASTNode): 386 | def __init__(self, qubit: QubitNode) -> None: 387 | super().__init__() 388 | self._qubit = qubit 389 | 390 | def get_qubit(self) -> QubitNode: 391 | return self._qubit 392 | 393 | def accept(self, visitor: 'IASTVisitor') -> None: 394 | visitor.on_reset(self) 395 | 396 | # 397 | ## 398 | # 399 | 400 | 401 | class IASTVisitor: 402 | 403 | def on_program(self, program: Program) -> None: 404 | for node in program._nodes: 405 | node.accept(self) 406 | 407 | def on_qubit_declaraion(self, declaration: QubitDeclarationNode) -> None: 408 | pass 409 | 410 | def on_qubit(self, qubit: QubitNode) -> None: 411 | pass 412 | 413 | def on_cvar(self, bit: CBitNode) -> None: 414 | pass 415 | 416 | def on_inv(self, inv: InvNode) -> None: 417 | inv.get_body().accept(self) 418 | 419 | def on_if_then_else(self, if_then_else: IfThenElseNode) -> None: 420 | if_then_else.get_condition().accept(self) 421 | if_then_else.get_then_body().accept(self) 422 | if_then_else.get_else_body().accept(self) 423 | 424 | def on_if_then(self, if_then) -> None: 425 | if_then.get_condition().accept(self) 426 | if_then.get_then_body().accept(self) 427 | 428 | def on_if_flip(self, if_flip: IfFlipNode) -> None: 429 | if_flip.get_condition().accept(self) 430 | 431 | def on_gate(self, node: GateNode) -> None: 432 | pass 433 | 434 | def on_match(self, match: MatchNode) -> None: 435 | pass 436 | 437 | def on_not(self, not_: NotNode) -> None: 438 | not_.get_condition().accept(self) 439 | 440 | def on_measure(self, measure: MeasurementNode) -> None: 441 | pass 442 | 443 | def on_reset(self, reset: ResetNode) -> None: 444 | pass 445 | -------------------------------------------------------------------------------- /quasar_cmd.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from abc import abstractmethod, ABC 24 | from typing import List, Optional, Set 25 | 26 | from builtin_gates import BuiltinGate, builtin_repr 27 | from quasar_formatter import IQAsmFormatter 28 | 29 | # 30 | ## 31 | # 32 | 33 | class ICmdVisitable(ABC): 34 | @abstractmethod 35 | def accept(self, visitor: 'ICmdVisitor') -> Optional['ICommand']: 36 | pass 37 | 38 | # 39 | ## 40 | # 41 | 42 | class ICommand(ICmdVisitable): 43 | @abstractmethod 44 | def get_lines(self, qasm_formatter: IQAsmFormatter) -> List[str]: 45 | pass 46 | 47 | @abstractmethod 48 | def get_target_qubit_id(self) -> int: 49 | pass 50 | 51 | 52 | class GateCmd(ICommand): 53 | def __init__( 54 | self, 55 | gate: BuiltinGate, 56 | target_qubit_id: int, 57 | control_qubit_ids: Set[int] = None, 58 | params: List[float] = None, 59 | ) -> None: 60 | self._gate = gate 61 | self._target_qubit_id = target_qubit_id 62 | self._params = params or [] 63 | self._control_qubit_ids = control_qubit_ids or set() 64 | 65 | def __eq__(self, other): 66 | if not isinstance(other, GateCmd): 67 | return False 68 | return (self._gate, self._target_qubit_id, self._params, self._control_qubit_ids) \ 69 | == (other._gate, other._target_qubit_id, other._params, other._control_qubit_ids) 70 | 71 | @property 72 | def gate(self) -> BuiltinGate: 73 | return self._gate 74 | 75 | def get_target_qubit_id(self) -> int: 76 | return self._target_qubit_id 77 | 78 | def get_control_qubit_ids(self) -> Set[int]: 79 | return self._control_qubit_ids 80 | 81 | def get_lines(self, qasm_formatter: IQAsmFormatter) -> List[str]: 82 | return [qasm_formatter.gate(self._gate, self._target_qubit_id, self._params, self._control_qubit_ids)] 83 | 84 | def accept(self, visitor: 'ICmdVisitor') -> None: 85 | visitor.on_gate(self) 86 | 87 | def __repr__(self): 88 | s = f'GateCmd({builtin_repr(self._gate)}, {self._target_qubit_id}' 89 | if self._control_qubit_ids: 90 | s += f', {repr(self._control_qubit_ids)}' 91 | if self._params: 92 | if not self._control_qubit_ids: 93 | s += f', set()' 94 | s += ')' 95 | return s 96 | 97 | 98 | class MeasurementCmd(ICommand): 99 | def __init__( 100 | self, 101 | qubit_id: int, 102 | bit_id: int 103 | ) -> None: 104 | self._qubit_id = qubit_id 105 | self._bit_id = bit_id 106 | 107 | def get_target_qubit_id(self) -> int: 108 | return self._qubit_id 109 | 110 | def get_target_bit_id(self) -> int: 111 | return self._bit_id 112 | 113 | def get_lines(self, qasm_formatter: IQAsmFormatter) -> List[str]: 114 | return [ 115 | qasm_formatter.measure(self._qubit_id, self._bit_id) 116 | ] 117 | 118 | def accept(self, visitor: 'ICmdVisitor') -> None: 119 | visitor.on_measurement(self) 120 | 121 | 122 | class ResetCmd(ICommand): 123 | def __init__( 124 | self, 125 | qubit_id: int 126 | ) -> None: 127 | self._qubit_id = qubit_id 128 | 129 | def get_target_qubit_id(self) -> int: 130 | return self._qubit_id 131 | 132 | def get_lines(self, qasm_formatter: IQAsmFormatter) -> List[str]: 133 | lines = qasm_formatter.reset(self._qubit_id).split('\n') 134 | return [line for line in lines] 135 | 136 | def accept(self, visitor: 'ICmdVisitor') -> None: 137 | visitor.on_reset(self) 138 | 139 | # 140 | ## 141 | # 142 | 143 | class ICmdVisitor(ABC): 144 | @abstractmethod 145 | def on_program(self, commands: List[ICommand]) -> None: 146 | for command in commands: 147 | command.accept(self) 148 | 149 | @abstractmethod 150 | def on_gate(self, cmd: GateCmd) -> None: 151 | pass 152 | 153 | @abstractmethod 154 | def on_measurement(self, m: MeasurementCmd) -> None: 155 | pass 156 | 157 | @abstractmethod 158 | def on_reset(self, reset: ResetCmd) -> None: 159 | pass 160 | -------------------------------------------------------------------------------- /quasar_comp.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | 24 | from copy import copy 25 | from typing import Callable, Dict, List, Tuple, Union 26 | 27 | from builtin_arithmetics import invert_gate 28 | from builtin_gates import X_GATE, Z_GATE 29 | from quasar_ast import \ 30 | QubitNode, QubitDeclarationNode, CBitNode, InvNode, IASTVisitor, Program, \ 31 | IfThenNode, IfThenElseNode, IfFlipNode, \ 32 | MatchNode, NotNode, \ 33 | MeasurementNode, ResetNode, IASTVisitable, GateNode, to_list 34 | from quasar_cmd import \ 35 | ICommand, MeasurementCmd, ResetCmd, GateCmd 36 | 37 | # Mapping from control qubit id onto (0, 1) 38 | # where 1 means positive control and 0 -- negative. 39 | _ControlQubits = Dict[int, Union[int, int]] 40 | 41 | 42 | class ResourceAllocator: 43 | def __init__(self) -> None: 44 | self.min_unused_qubit_id = 0 # the first currently available to use qubit id 45 | self.qubits_counter = 0 # the first qubit id that is never used up to the current moment 46 | self.bits_counter = 0 # the first bit id that is never used up to the current moment 47 | 48 | def get_qubits_counter(self) -> int: 49 | return self.qubits_counter 50 | 51 | def get_min_unused_qubit_id(self) -> int: 52 | return self.min_unused_qubit_id 53 | 54 | def get_bits_counter(self) -> int: 55 | return self.bits_counter 56 | 57 | def allocate_qubit(self) -> int: 58 | self.min_unused_qubit_id += 1 59 | self.qubits_counter = max(self.qubits_counter, self.min_unused_qubit_id) 60 | return self.min_unused_qubit_id - 1 61 | 62 | def free_qubit(self) -> None: 63 | self.min_unused_qubit_id -= 1 64 | 65 | def free_qubits(self, qubits) -> None: 66 | for _ in range(qubits): 67 | self.free_qubit() 68 | 69 | def allocate_bit(self) -> int: 70 | self.bits_counter += 1 71 | return self.bits_counter - 1 72 | 73 | 74 | class CompileVisitor(IASTVisitor): 75 | def __init__(self, rsrc) -> None: 76 | self._rsrc = rsrc 77 | self._commands: List[ICommand] = [] 78 | self._control_mapping: _ControlQubits = {} # The dict of currently controlling qubits 79 | 80 | @staticmethod 81 | def _invert_control_qubits(control_qubits: _ControlQubits) -> _ControlQubits: 82 | """ Most likely you need to assert if its length is equal to 1 before applying. """ 83 | return {q: 0 if m == 1 else 1 for q, m in control_qubits.items()} 84 | 85 | @staticmethod 86 | def _get_reduced_commands( 87 | max_num_qubits, 88 | control_mapping, 89 | rsrc 90 | ) -> List[ICommand]: 91 | """ Ensures that the number of control qubits is at most one. 92 | This is usually required when the condition is going to be negated. """ 93 | if len(control_mapping) <= max_num_qubits: 94 | return [] 95 | 96 | control_qubit_ids, cccu_commands = CompileVisitor._get_cccu_commands( 97 | control_mapping, 98 | rsrc, 99 | max_num_qubits 100 | ) 101 | 102 | control_mapping.clear() 103 | control_mapping.update(control_qubit_ids) 104 | return cccu_commands 105 | 106 | @staticmethod 107 | def _get_qubit_negation_commands(control_mapping) -> List[ICommand]: 108 | """ This function makes sure that all control qubits 109 | are in fact positively controlling. """ 110 | result : List[ICommand] = [] 111 | 112 | for qubit_id, mask in control_mapping.items(): 113 | if mask == 0: 114 | result.append(GateCmd(X_GATE, qubit_id)) 115 | control_mapping[qubit_id] = 1 116 | 117 | return result 118 | 119 | @staticmethod 120 | def _get_cccu_commands( 121 | control_mapping: _ControlQubits, 122 | rsrc: ResourceAllocator, 123 | max_num_qubits: int 124 | ) -> Tuple[_ControlQubits, List[ICommand]]: 125 | """ 126 | Builds a circuit that computes the AND condition on the `control_mapping` 127 | qubits (some possibly negated) into one qubit. 128 | To acheive this, when the number of control bits is greater than `max_num_qubits`, 129 | an `ancilla_allocator` may be used to allocate extra qubits. 130 | The resulting computation consists of X gates (for negation) and CCX gates to 131 | construct a computation tree. This tree has a logarithmic depth. 132 | Returns a tuple consisting of a new control qubits dict and the computation commands. 133 | """ 134 | 135 | commands: List[ICommand] = [] 136 | 137 | for (qubit_id, is_positive) in control_mapping.items(): 138 | if is_positive == 0: 139 | commands.append(GateCmd(X_GATE, qubit_id)) 140 | 141 | control_qubit_ids = list(control_mapping) 142 | 143 | while len(control_qubit_ids) > max_num_qubits: 144 | q1 = control_qubit_ids.pop(0) 145 | q2 = control_qubit_ids.pop(0) 146 | q3 = rsrc.allocate_qubit() 147 | commands.append(GateCmd(X_GATE, q3, control_qubit_ids={q1, q2})) 148 | control_qubit_ids.append(q3) 149 | 150 | return {q: 1 for q in control_qubit_ids}, commands 151 | 152 | @property 153 | def commands(self) -> List[ICommand]: 154 | return self._commands 155 | 156 | def get_max_used_qubit_id(self) -> int: 157 | return self._rsrc.get_qubits_counter() 158 | 159 | def get_max_used_bit_id(self) -> int: 160 | return self._rsrc.get_bits_counter() 161 | 162 | def _get_commands_recursive( 163 | self, 164 | visitable: IASTVisitable, 165 | with_controls: _ControlQubits = None 166 | ) -> List[ICommand]: 167 | """ Gets the list of commands that is recursively generated by visiting `visitable`. 168 | Additional `with_controls` dict can be used to use additional controls 169 | in addition to the currently set.""" 170 | 171 | with_controls = with_controls or {} 172 | subvisitor = CompileVisitor(self._rsrc) 173 | subvisitor._control_mapping = copy(self._control_mapping) 174 | assert not (set(with_controls) & set(subvisitor._control_mapping)) 175 | subvisitor._control_mapping.update(with_controls) 176 | visitable.accept(subvisitor) 177 | return subvisitor._commands 178 | 179 | def on_qubit_declaraion(self, declaration: QubitDeclarationNode) -> None: 180 | declaration.get_qubit().set_target_qubit_id(self._rsrc.allocate_qubit()) 181 | 182 | def on_qubit(self, qubit: QubitNode) -> None: 183 | self._control_mapping[qubit.get_id()] = 1 184 | 185 | def on_cvar(self, bit: CBitNode) -> None: 186 | bit.set_id(self._rsrc.allocate_bit()) 187 | 188 | def on_inv(self, inv: InvNode) -> None: 189 | self._commands.extend(self._inversed(self._get_commands_recursive(inv.get_body()))) 190 | 191 | def on_if_then(self, if_then: IfThenNode) -> None: 192 | qubits_counter = self._rsrc.get_min_unused_qubit_id() 193 | cvis = CompileVisitor(self._rsrc) 194 | if_then.get_condition().accept(cvis) 195 | if_commands = cvis._commands 196 | 197 | then_commands = self._get_commands_recursive(if_then.get_then_body(), cvis._control_mapping) 198 | 199 | self._commands.extend( 200 | if_commands + 201 | then_commands + 202 | self._inversed(if_commands) 203 | ) 204 | 205 | num_ancillas = self._rsrc.get_min_unused_qubit_id() - qubits_counter 206 | self._rsrc.free_qubits(num_ancillas) 207 | 208 | def on_if_then_else(self, if_then_else: IfThenElseNode) -> None: 209 | qubits_counter = self._rsrc.get_min_unused_qubit_id() 210 | cvis = CompileVisitor(self._rsrc) 211 | if_then_else.get_condition().accept(cvis) 212 | 213 | cvis._commands.extend( 214 | CompileVisitor._get_reduced_commands( 215 | 1, 216 | cvis._control_mapping, 217 | cvis._rsrc 218 | ) 219 | ) 220 | 221 | if_commands = cvis._commands 222 | 223 | then_commands: List[ICommand] = self._get_commands_recursive( 224 | if_then_else.get_then_body(), 225 | cvis._control_mapping 226 | ) 227 | 228 | if len(cvis._control_mapping) == 0: # If(All([])).Then(...).Else(...) 229 | self._commands.extend( 230 | if_commands + 231 | then_commands + 232 | self._inversed(if_commands) 233 | ) 234 | 235 | elif len(cvis._control_mapping) == 1: # Only one qubit could be easily inverted 236 | else_commands: List[ICommand] = self._get_commands_recursive( 237 | if_then_else.get_else_body(), 238 | CompileVisitor._invert_control_qubits(cvis._control_mapping) 239 | ) 240 | 241 | self._commands.extend( 242 | if_commands + 243 | then_commands + 244 | else_commands + 245 | self._inversed(if_commands) 246 | ) 247 | 248 | else: 249 | assert(False) 250 | 251 | num_ancillas = self._rsrc.get_min_unused_qubit_id() - qubits_counter 252 | self._rsrc.free_qubits(num_ancillas) 253 | 254 | def on_if_flip(self, if_flip: IfFlipNode) -> None: 255 | qubits_counter = self._rsrc.get_min_unused_qubit_id() 256 | cvis = CompileVisitor(self._rsrc) 257 | if_flip.get_condition().accept(cvis) 258 | 259 | cvis._commands.extend( 260 | CompileVisitor._get_reduced_commands( 261 | 2, 262 | cvis._control_mapping, 263 | cvis._rsrc 264 | ) 265 | ) 266 | 267 | cvis._commands.extend( 268 | CompileVisitor._get_qubit_negation_commands(cvis._control_mapping) 269 | ) 270 | control_qubit_ids = list(cvis._control_mapping) 271 | 272 | if_commands: List[ICommand] = cvis._commands 273 | 274 | flip_command = GateCmd( 275 | Z_GATE, 276 | control_qubit_ids[-1], 277 | control_qubit_ids=set(control_qubit_ids[:-1]) 278 | ) 279 | 280 | self._commands.extend( 281 | if_commands + 282 | [flip_command] + 283 | self._inversed(if_commands) 284 | ) 285 | 286 | num_ancillas = self._rsrc.get_min_unused_qubit_id() - qubits_counter 287 | self._rsrc.free_qubits(num_ancillas) 288 | 289 | def _get_on_gate_commands(self, node: GateNode) -> List[ICommand]: 290 | if not self._control_mapping: 291 | return [GateCmd(node.gate, node.get_target_qubit_id(), params=node.params)] 292 | 293 | negate_negative_commands : List[ICommand] = [] 294 | 295 | for qubit_id, mask in self._control_mapping.items(): 296 | if mask == 0: 297 | negate_negative_commands.append(GateCmd(X_GATE, qubit_id)) 298 | 299 | num_ancillas = 0 300 | commands: List[ICommand] 301 | control_commands: List[ICommand] = [] 302 | control_qubit_ids = list(self._control_mapping) 303 | max_controls = 2 if node.gate == X_GATE else 1 # TODO(adsz): to be defined by gate / target architecture 304 | 305 | while len(control_qubit_ids) > max_controls: 306 | control_1 = control_qubit_ids.pop(0) 307 | control_2 = control_qubit_ids.pop(0) 308 | ancilla = self._rsrc.allocate_qubit() 309 | num_ancillas += 1 310 | control_qubit_ids.append(ancilla) 311 | control_commands.append( 312 | GateCmd(X_GATE, ancilla, control_qubit_ids={control_1, control_2}) 313 | ) 314 | 315 | controlled_command = GateCmd( 316 | node.gate, 317 | node.get_target_qubit_id(), 318 | params=node.params, 319 | control_qubit_ids=set(control_qubit_ids) 320 | ) 321 | 322 | commands = ( 323 | control_commands + 324 | [controlled_command] + 325 | self._inversed(control_commands) 326 | ) 327 | 328 | self._rsrc.free_qubits(num_ancillas) 329 | 330 | return ( 331 | negate_negative_commands + 332 | commands + 333 | self._inversed(negate_negative_commands) 334 | ) 335 | 336 | def on_gate(self, node: GateNode) -> None: 337 | self._commands.extend(self._get_on_gate_commands(node)) 338 | 339 | def _inversed(self, commands: Union[ICommand, List[ICommand]]) -> List[ICommand]: 340 | def inverse_command(command: ICommand): 341 | if not isinstance(command, GateCmd): 342 | raise ValueError(f"Inverse of {type(command)} is impossible.") 343 | 344 | inv_gate, inv_params = invert_gate(command._gate, command._params) 345 | 346 | return GateCmd( 347 | inv_gate, 348 | command.get_target_qubit_id(), 349 | command.get_control_qubit_ids(), 350 | inv_params 351 | ) 352 | 353 | return [inverse_command(command) for command in to_list(commands)[::-1]] 354 | 355 | def on_match(self, match: MatchNode) -> None: 356 | controls: List[QubitNode] = match.get_control_qubits() 357 | mask: List[int] = match.get_mask() 358 | 359 | for (control, bit) in zip(controls, mask): 360 | subvisitor = CompileVisitor(self._rsrc) 361 | control.accept(subvisitor) 362 | self._commands.extend(subvisitor._commands) 363 | assert len(subvisitor._control_mapping) == 1 364 | 365 | if bit == 1: 366 | self._control_mapping.update(subvisitor._control_mapping) 367 | elif bit == 0: 368 | assert len(subvisitor._control_mapping) <= 1 369 | self._control_mapping.update( 370 | CompileVisitor._invert_control_qubits(subvisitor._control_mapping) 371 | ) 372 | else: 373 | raise Exception("Syntax error") 374 | 375 | def on_not(self, not_: NotNode) -> None: 376 | subvisitor = CompileVisitor(self._rsrc) 377 | not_.get_condition().accept(subvisitor) 378 | 379 | if len(subvisitor._control_mapping) <= 1: 380 | self._control_mapping.update( 381 | CompileVisitor._invert_control_qubits(subvisitor._control_mapping)) 382 | else: 383 | control_bits, cccu_commands = CompileVisitor._get_cccu_commands( 384 | subvisitor._control_mapping, 385 | self._rsrc, 386 | max_num_qubits=1 387 | ) 388 | assert len(control_bits) == 1 389 | result_bit = list(control_bits)[0] 390 | 391 | self._commands.extend(cccu_commands) 392 | self._control_mapping[result_bit] = 0 393 | 394 | def on_measure(self, measure: MeasurementNode) -> None: 395 | self._commands.append( 396 | MeasurementCmd( 397 | measure.get_qubit().get_id(), 398 | measure.get_bit().get_id() 399 | ) 400 | ) 401 | 402 | def on_reset(self, reset: ResetNode) -> None: 403 | self._commands.append(ResetCmd(reset.get_qubit().get_id())) 404 | -------------------------------------------------------------------------------- /quasar_formatter.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from abc import abstractmethod, ABC 24 | from typing import List, Set, TypeVar, Dict 25 | 26 | from builtin_gates import BuiltinGate 27 | 28 | 29 | class IQAsmFormatter(ABC): 30 | def __init__(self): 31 | self.qubits_counter = 0 32 | self.bits_counter = 0 33 | self.groups : List[int] = [] 34 | 35 | def set_qubits_counter(self, qubits_counter: int): 36 | self.qubits_counter = qubits_counter 37 | 38 | def set_bits_counter(self, bits_counter: int): 39 | self.bits_counter = bits_counter 40 | 41 | def set_groups(self, groups: List[int]): 42 | self.groups = groups 43 | 44 | @abstractmethod 45 | def get_headers(self) -> List[str]: 46 | pass 47 | 48 | @abstractmethod 49 | def get_footers(self) -> List[str]: 50 | pass 51 | 52 | @abstractmethod 53 | def gate(self, gate: BuiltinGate, qubit: int, params: List[float], control_qubit_ids: Set[int]) -> str: 54 | pass 55 | 56 | @abstractmethod 57 | def measure(self, qubit: int, bit: int) -> str: 58 | pass 59 | 60 | @abstractmethod 61 | def reset(self, qubit: int) -> str: 62 | pass 63 | 64 | T = TypeVar('T') 65 | def _get_for_gate(self, mapping: Dict[BuiltinGate, List[T]], gate: BuiltinGate, num_controls: int) -> T: 66 | if gate not in mapping: 67 | raise ValueError(f'Gate {gate} not supported') 68 | if len(mapping[gate]) < num_controls: 69 | raise ValueError(f'Gate {gate} cannot have at max {num_controls} control qubits.') 70 | return mapping[gate][num_controls] 71 | 72 | -------------------------------------------------------------------------------- /quasar_opt.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from itertools import chain 24 | from typing import List, Set, Tuple 25 | 26 | from builtin_arithmetics import invert_gate 27 | from quasar_cmd import ICommand, ICmdVisitor, \ 28 | GateCmd, MeasurementCmd, ResetCmd 29 | 30 | 31 | class _CmdStackInserterVisitor(ICmdVisitor): 32 | def run( 33 | self, 34 | commands: List[ICommand], 35 | commands_stack: List[List[Tuple[int, ICommand]]] 36 | ) -> List[List[Tuple[int, ICommand]]]: 37 | 38 | # 39 | # Stacks for individual qubits, representing all the gates/operations/... 40 | # affecting that qubit, putting the latest applied operation on the top of the stack. 41 | # 42 | # Prog: X(0), X(1), CX(1, 2), CCX(0, 1, 2), ... 43 | # 44 | # [CCX(0,1,2)] 45 | # [CCX(0,1,2)][CX(1,2) ][CCX(0,1,2)] 46 | # [X(0) ][X(1) ][CX(1,2) ] 47 | # [----Q0----][----Q1----][----Q2----]...[----QN----] 48 | # 49 | self._commands_stack = commands_stack 50 | 51 | self._id = 0 52 | 53 | for (id_, command) in enumerate(commands): 54 | self.set_id(id_) 55 | command.accept(self) 56 | 57 | return self._commands_stack 58 | 59 | def set_id(self, id_: int) -> None: 60 | self._id = id_ 61 | 62 | def on_gate(self, cmd: GateCmd) -> None: 63 | affected_qubit_ids: Set[int] = {cmd._target_qubit_id} | cmd._control_qubit_ids 64 | 65 | inverse_gate, inverse_params = invert_gate(cmd._gate, cmd._params) 66 | 67 | eliminate = True 68 | for qubit_id in affected_qubit_ids: 69 | if not self._commands_stack[qubit_id]: 70 | eliminate = False 71 | continue 72 | _, last_cmd = self._commands_stack[qubit_id][-1] 73 | if not isinstance(last_cmd, GateCmd): 74 | eliminate = False 75 | continue 76 | if last_cmd._control_qubit_ids != cmd._control_qubit_ids: 77 | eliminate = False 78 | if last_cmd._target_qubit_id != cmd._target_qubit_id: 79 | eliminate = False 80 | if last_cmd._gate != inverse_gate: 81 | eliminate = False 82 | if last_cmd._params != inverse_params: 83 | # TODO(adsz): Allow approx. 84 | eliminate = False 85 | 86 | if eliminate: 87 | for qubit_id in affected_qubit_ids: 88 | self._commands_stack[qubit_id].pop() 89 | else: 90 | for qubit_id in affected_qubit_ids: 91 | self._commands_stack[qubit_id].append((self._id, cmd)) 92 | 93 | def on_program(self, commands: List[ICommand]) -> None: 94 | pass 95 | 96 | def on_measurement(self, m: MeasurementCmd) -> None: 97 | self._commands_stack[m.get_target_qubit_id()].append((self._id, m)) 98 | 99 | def on_reset(self, reset: ResetCmd) -> None: 100 | self._commands_stack[reset.get_target_qubit_id()].append((self._id, reset)) 101 | 102 | # 103 | ## 104 | # 105 | 106 | class QuasarOpt: 107 | @staticmethod 108 | def _serialize(commands_stacks: List[List[Tuple[int, ICommand]]]) -> List[ICommand]: 109 | flat = chain.from_iterable(commands_stacks) 110 | unique = dict(flat).items() 111 | return [cmd for (_, cmd) in sorted(unique)] 112 | 113 | @staticmethod 114 | def run(commands: List[ICommand], max_used_qubit_id: int) -> List[ICommand]: 115 | commands_stacks: List[List[Tuple[int, ICommand]]] = [[] for _ in range(max_used_qubit_id + 1)] 116 | commands_stacks = _CmdStackInserterVisitor().run(commands, commands_stacks) 117 | return QuasarOpt._serialize(commands_stacks) 118 | -------------------------------------------------------------------------------- /quasar_opt_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import List 24 | import unittest 25 | 26 | from builtin_gates import X_GATE, U3_GATE, Y_GATE, Z_GATE, H_GATE 27 | from quasar_cmd import ICommand, ResetCmd, MeasurementCmd, GateCmd 28 | from quasar_opt import QuasarOpt 29 | 30 | # 31 | ## 32 | # 33 | 34 | class QCompilerTest(unittest.TestCase): 35 | def __init__(self, *args) -> None: 36 | super().__init__(*args) 37 | 38 | def _test_one_qubit_command_1(self, cmd_class) -> None: 39 | min_unused_qubit_id = 2 40 | cmd_1: ICommand = GateCmd(cmd_class, 0) 41 | cmd_2: ICommand = GateCmd(cmd_class, 0) 42 | commands: List[ICommand] = [cmd_1, cmd_2] 43 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 44 | expected: List[ICommand] = [] 45 | self.assertListEqual(actual, expected) 46 | 47 | def _test_one_qubit_command_2(self, cmd_class) -> None: 48 | min_unused_qubit_id = 2 49 | cmd_1: ICommand = GateCmd(cmd_class, 0) 50 | cmd_2: ICommand = GateCmd(cmd_class, 1) 51 | cmd_3: ICommand = GateCmd(cmd_class, 1) 52 | cmd_4: ICommand = GateCmd(cmd_class, 0) 53 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 54 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 55 | expected: List[ICommand] = [] 56 | self.assertListEqual(actual, expected) 57 | 58 | def _test_one_qubit_command_3(self, cmd_class) -> None: 59 | min_unused_qubit_id = 2 60 | cmd_1: ICommand = GateCmd(cmd_class, 0) 61 | cmd_2: ICommand = GateCmd(cmd_class, 1) 62 | commands: List[ICommand] = [cmd_1,cmd_2] 63 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 64 | expected: List[ICommand] = [cmd_1, cmd_2] 65 | self.assertListEqual(actual, expected) 66 | 67 | def _test_one_qubit_command_4(self, cmd_class) -> None: 68 | min_unused_qubit_id = 3 69 | cmd_1: ICommand = GateCmd(cmd_class, 0) 70 | cmd_2: ICommand = GateCmd(cmd_class, 1) 71 | cmd_3: ICommand = GateCmd(cmd_class, 1) 72 | cmd_4: ICommand = GateCmd(cmd_class, 2) 73 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 74 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 75 | expected: List[ICommand] = [cmd_1, cmd_4] 76 | self.assertListEqual(actual, expected) 77 | 78 | def _test_one_qubit_commands_1(self, cmd_class_1, cmd_class_2) -> None: 79 | min_unused_qubit_id = 4 80 | cmd_1: ICommand = GateCmd(cmd_class_1, 0) 81 | cmd_2: ICommand = GateCmd(cmd_class_2, 1) 82 | cmd_3: ICommand = GateCmd(cmd_class_1, 2) 83 | cmd_4: ICommand = GateCmd(cmd_class_2, 3) 84 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 85 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 86 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 87 | self.assertListEqual(actual, expected) 88 | 89 | def _test_one_qubit_commands_2(self, cmd_class_1, cmd_class_2) -> None: 90 | min_unused_qubit_id = 1 91 | cmd_1: ICommand = GateCmd(cmd_class_1, 0) 92 | cmd_2: ICommand = GateCmd(cmd_class_2, 0) 93 | cmd_3: ICommand = GateCmd(cmd_class_2, 0) 94 | cmd_4: ICommand = GateCmd(cmd_class_1, 0) 95 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 96 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 97 | expected: List[ICommand] = [] 98 | self.assertListEqual(actual, expected) 99 | 100 | def _test_one_qubit_command_7(self, cmd_class_1, cmd_class_2) -> None: 101 | min_unused_qubit_id = 2 102 | cmd_1: ICommand = GateCmd(cmd_class_1, 0) 103 | cmd_2: ICommand = GateCmd(cmd_class_2, 1) 104 | cmd_3: ICommand = GateCmd(cmd_class_2, 1) 105 | cmd_4: ICommand = GateCmd(cmd_class_1, 0) 106 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 107 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 108 | expected: List[ICommand] = [] 109 | self.assertListEqual(actual, expected) 110 | 111 | def _test_two_qubits_command_1(self, cmd_class) -> None: 112 | min_unused_qubit_id = 2 113 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 114 | cmd_2: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 115 | commands: List[ICommand] = [cmd_1, cmd_2] 116 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 117 | expected: List[ICommand] = [] 118 | self.assertListEqual(actual, expected) 119 | 120 | def _test_two_qubits_command_2(self, cmd_class) -> None: 121 | min_unused_qubit_id = 3 122 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 123 | cmd_2: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={1}) 124 | commands: List[ICommand] = [cmd_1, cmd_2] 125 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 126 | expected: List[ICommand] = [cmd_1, cmd_2] 127 | self.assertListEqual(actual, expected) 128 | 129 | def _test_two_qubits_command_3(self, cmd_class) -> None: 130 | min_unused_qubit_id = 3 131 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 132 | cmd_2: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={1}) 133 | cmd_3: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 134 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 135 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 136 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 137 | self.assertListEqual(actual, expected) 138 | 139 | def _test_two_qubits_command_4(self, cmd_class) -> None: 140 | min_unused_qubit_id = 3 141 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 142 | cmd_2: ICommand = GateCmd(cmd_class, 0, control_qubit_ids={1}) 143 | commands: List[ICommand] = [cmd_1, cmd_2] 144 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 145 | expected: List[ICommand] = [cmd_1, cmd_2] 146 | self.assertListEqual(actual, expected) 147 | 148 | def _test_two_qubits_command_5(self, cmd_class) -> None: 149 | min_unused_qubit_id = 3 150 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 151 | cmd_2: ICommand = GateCmd(cmd_class, 0, control_qubit_ids={1}) 152 | commands: List[ICommand] = [cmd_1, cmd_2] 153 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 154 | expected: List[ICommand] = [cmd_1, cmd_2] 155 | self.assertListEqual(actual, expected) 156 | 157 | def _test_two_qubits_command_6(self, cmd_class) -> None: 158 | min_unused_qubit_id = 4 159 | cmd_1: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={0}) 160 | cmd_2: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={2}) 161 | cmd_3: ICommand = GateCmd(cmd_class, 1, control_qubit_ids={2}) 162 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 163 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 164 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 165 | self.assertListEqual(actual, expected) 166 | 167 | def _test_two_qubits_commands_1(self, cmd_class_1, cmd_class_2) -> None: 168 | min_unused_qubit_id = 2 169 | cmd_1: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 170 | cmd_2: ICommand = GateCmd(cmd_class_2, 1, control_qubit_ids={0}) 171 | cmd_3: ICommand = GateCmd(cmd_class_2, 1, control_qubit_ids={0}) 172 | cmd_4: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 173 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 174 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 175 | expected: List[ICommand] = [] 176 | self.assertListEqual(actual, expected) 177 | 178 | def _test_two_qubits_commands_2(self, cmd_class_1, cmd_class_2) -> None: 179 | min_unused_qubit_id = 3 180 | cmd_1: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 181 | cmd_2: ICommand = GateCmd(cmd_class_2, 2, control_qubit_ids={1}) 182 | cmd_3: ICommand = GateCmd(cmd_class_2, 2, control_qubit_ids={1}) 183 | cmd_4: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 184 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 185 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 186 | expected: List[ICommand] = [] 187 | self.assertListEqual(actual, expected) 188 | 189 | def _test_two_qubits_commands_3(self, cmd_class_1, cmd_class_2) -> None: 190 | min_unused_qubit_id = 2 191 | cmd_1: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 192 | cmd_2: ICommand = GateCmd(cmd_class_2, 0, control_qubit_ids={1}) 193 | cmd_3: ICommand = GateCmd(cmd_class_2, 1, control_qubit_ids={0}) 194 | cmd_4: ICommand = GateCmd(cmd_class_1, 0, control_qubit_ids={1}) 195 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 196 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 197 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 198 | self.assertListEqual(actual, expected) 199 | 200 | def _test_three_qubits_command_1(self, cmd_class) -> None: 201 | min_unused_qubit_id = 3 202 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 203 | cmd_2: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 204 | commands: List[ICommand] = [cmd_1, cmd_2] 205 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 206 | expected: List[ICommand] = [] 207 | self.assertListEqual(actual, expected) 208 | 209 | def _test_three_qubits_command_2(self, cmd_class) -> None: 210 | min_unused_qubit_id = 4 211 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 212 | cmd_2: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={1, 2}) 213 | cmd_3: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={1, 2}) 214 | cmd_4: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 215 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 216 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 217 | expected: List[ICommand] = [] 218 | self.assertListEqual(actual, expected) 219 | 220 | def _test_three_qubits_command_3(self, cmd_class) -> None: 221 | min_unused_qubit_id = 4 222 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 223 | cmd_2: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={1, 2}) 224 | commands: List[ICommand] = [cmd_1,cmd_2] 225 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 226 | expected: List[ICommand] = [cmd_1, cmd_2] 227 | self.assertListEqual(actual, expected) 228 | 229 | def _test_three_qubits_command_4(self, cmd_class) -> None: 230 | min_unused_qubit_id = 5 231 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 232 | cmd_2: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={1, 2}) 233 | cmd_3: ICommand = GateCmd(cmd_class, 3, control_qubit_ids={1, 2}) 234 | cmd_4: ICommand = GateCmd(cmd_class, 4, control_qubit_ids={2, 3}) 235 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3, cmd_4] 236 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 237 | expected: List[ICommand] = [cmd_1, cmd_4] 238 | self.assertListEqual(actual, expected) 239 | 240 | def _test_three_qubits_command_5(self, cmd_class) -> None: 241 | min_unused_qubit_id = 5 242 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 243 | cmd_2: ICommand = GateCmd(cmd_class, 0, control_qubit_ids={2, 1}) 244 | commands: List[ICommand] = [cmd_1, cmd_2] 245 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 246 | expected: List[ICommand] = [cmd_1, cmd_2] 247 | self.assertListEqual(actual, expected) 248 | 249 | def _test_three_qubits_command_6(self, cmd_class) -> None: 250 | min_unused_qubit_id = 3 251 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 252 | cmd_2: ICommand = GateCmd(X_GATE, 0) 253 | cmd_3: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 254 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 255 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 256 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 257 | self.assertListEqual(actual, expected) 258 | 259 | def _test_three_qubits_command_7(self, cmd_class) -> None: 260 | min_unused_qubit_id = 3 261 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 262 | cmd_2: ICommand = GateCmd(X_GATE, 1) 263 | cmd_3: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 264 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 265 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 266 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 267 | self.assertListEqual(actual, expected) 268 | 269 | def _test_three_qubits_command_8(self, cmd_class) -> None: 270 | min_unused_qubit_id = 3 271 | cmd_1: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 272 | cmd_2: ICommand = GateCmd(X_GATE, 2) 273 | cmd_3: ICommand = GateCmd(cmd_class, 2, control_qubit_ids={0, 1}) 274 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 275 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 276 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 277 | self.assertListEqual(actual, expected) 278 | 279 | def _test_one_qubit_reset_command_1(self, cmd_class_1, cmd_class_2) -> None: 280 | min_unused_qubit_id = 2 281 | cmd_1: ICommand = GateCmd(cmd_class_1, 0) 282 | cmd_2: ICommand = cmd_class_2(0) 283 | cmd_3: ICommand = GateCmd(cmd_class_1, 0) 284 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 285 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 286 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 287 | self.assertListEqual(actual, expected) 288 | 289 | def _test_two_qubits_reset_command_1(self, cmd_class_1, cmd_class_2) -> None: 290 | min_unused_qubit_id = 2 291 | cmd_1: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 292 | cmd_2: ICommand = cmd_class_2(0) 293 | cmd_3: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 294 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 295 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 296 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 297 | self.assertListEqual(actual, expected) 298 | 299 | def _test_two_qubits_reset_command_2(self, cmd_class_1, cmd_class_2) -> None: 300 | min_unused_qubit_id = 2 301 | cmd_1: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 302 | cmd_2: ICommand = GateCmd(cmd_class_2, 1) 303 | cmd_3: ICommand = GateCmd(cmd_class_1, 1, control_qubit_ids={0}) 304 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 305 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 306 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 307 | self.assertListEqual(actual, expected) 308 | 309 | def _test_three_qubits_reset_command_1(self, cmd_class_1, cmd_class_2) -> None: 310 | min_unused_qubit_id = 3 311 | cmd_1: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 312 | cmd_2: ICommand = cmd_class_2(0) 313 | cmd_3: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 314 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 315 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 316 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 317 | self.assertListEqual(actual, expected) 318 | 319 | def _test_three_qubits_reset_command_2(self, cmd_class_1, cmd_class_2) -> None: 320 | min_unused_qubit_id = 3 321 | cmd_1: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 322 | cmd_2: ICommand = cmd_class_2(1) 323 | cmd_3: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 324 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 325 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 326 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 327 | self.assertListEqual(actual, expected) 328 | 329 | def _test_three_qubits_reset_command_3(self, cmd_class_1, cmd_class_2) -> None: 330 | min_unused_qubit_id = 3 331 | cmd_1: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 332 | cmd_2: ICommand = cmd_class_2(2) 333 | cmd_3: ICommand = GateCmd(cmd_class_1, 2, control_qubit_ids={0, 1}) 334 | commands: List[ICommand] = [cmd_1, cmd_2, cmd_3] 335 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 336 | expected: List[ICommand] = [cmd_1, cmd_2, cmd_3] 337 | self.assertListEqual(actual, expected) 338 | 339 | def _test_one_qubit_u3_command_1(self) -> None: 340 | min_unused_qubit_id = 1 341 | cmd_1: ICommand = GateCmd(U3_GATE, 0, params=[1.1, 2.2, 3.3]) 342 | cmd_2: ICommand = GateCmd(U3_GATE, 0, params=[-1.1, -3.3, -2.2]) 343 | commands: List[ICommand] = [cmd_1, cmd_2] 344 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 345 | expected: List[ICommand] = [] 346 | self.assertListEqual(actual, expected) 347 | 348 | def _test_one_qubit_u3_command_2(self) -> None: 349 | min_unused_qubit_id = 1 350 | cmd_1: ICommand = GateCmd(U3_GATE, 0, params=[1.1, 2.2, 3.3]) 351 | cmd_2: ICommand = GateCmd(U3_GATE, 0, params=[1.1, 3.3, 2.2]) 352 | commands: List[ICommand] = [cmd_1, cmd_2] 353 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 354 | expected: List[ICommand] = [cmd_1, cmd_2] 355 | self.assertListEqual(actual, expected) 356 | 357 | def _test_two_qubits_cu3_command_1(self) -> None: 358 | min_unused_qubit_id = 2 359 | cmd_1: ICommand = GateCmd(U3_GATE, 1, params=[2.2, 3.3, 4.4], control_qubit_ids={0}) 360 | cmd_2: ICommand = GateCmd(U3_GATE, 1, params=[-2.2, -4.4, -3.3], control_qubit_ids={0}) 361 | commands: List[ICommand] = [cmd_1, cmd_2] 362 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 363 | expected: List[ICommand] = [] 364 | self.assertListEqual(actual, expected) 365 | 366 | def _test_two_qubits_cu3_command_2(self) -> None: 367 | min_unused_qubit_id = 2 368 | cmd_1: ICommand = GateCmd(U3_GATE, 1, params=[2.2, 3.3, 4.4], control_qubit_ids={0}) 369 | cmd_2: ICommand = GateCmd(U3_GATE, 1, params=[2.2, 3.3, 4.4], control_qubit_ids={0}) 370 | commands: List[ICommand] = [cmd_1, cmd_2] 371 | actual: List[ICommand] = QuasarOpt().run(commands, min_unused_qubit_id) 372 | expected: List[ICommand] = [cmd_1, cmd_2] 373 | self.assertListEqual(actual, expected) 374 | 375 | def test_optimizer(self) -> None: 376 | builtins = (X_GATE, Y_GATE, Z_GATE, H_GATE) 377 | builtins2 = (Y_GATE, Z_GATE, H_GATE, X_GATE) 378 | 379 | for cmd_1q_class in builtins: 380 | self._test_one_qubit_command_1(cmd_1q_class) 381 | self._test_one_qubit_command_2(cmd_1q_class) 382 | self._test_one_qubit_command_3(cmd_1q_class) 383 | self._test_one_qubit_command_4(cmd_1q_class) 384 | 385 | for (cmd_1q_class_1, cmd_1q_class_2) in zip(builtins, builtins2): 386 | self._test_one_qubit_commands_1(cmd_1q_class_1, cmd_1q_class_2) 387 | self._test_one_qubit_commands_2(cmd_1q_class_1, cmd_1q_class_2) 388 | 389 | self._test_one_qubit_u3_command_1() 390 | self._test_one_qubit_u3_command_2() 391 | 392 | def _MeasurementCmdWrapper(qubit_id): 393 | return MeasurementCmd(qubit_id=qubit_id, bit_id=0) 394 | 395 | for cmd_reset in [ResetCmd, _MeasurementCmdWrapper]: 396 | for cmd_1q_class in builtins: 397 | self._test_one_qubit_reset_command_1(cmd_1q_class, cmd_reset) 398 | 399 | for cmd_2q_class in builtins: 400 | self._test_two_qubits_command_1(cmd_2q_class) 401 | self._test_two_qubits_command_2(cmd_2q_class) 402 | self._test_two_qubits_command_3(cmd_2q_class) 403 | self._test_two_qubits_command_4(cmd_2q_class) 404 | self._test_two_qubits_command_5(cmd_2q_class) 405 | self._test_two_qubits_command_6(cmd_2q_class) 406 | 407 | for (cmd_2q_class_1, cmd_2q_class_2) in zip(builtins, builtins2): 408 | self._test_two_qubits_commands_1(cmd_2q_class_1, cmd_2q_class_2) 409 | self._test_two_qubits_commands_2(cmd_2q_class_1, cmd_2q_class_2) 410 | self._test_two_qubits_commands_3(cmd_2q_class_1, cmd_2q_class_2) 411 | 412 | self._test_two_qubits_cu3_command_1() 413 | self._test_two_qubits_cu3_command_2() 414 | 415 | for cmd_reset in [ResetCmd, _MeasurementCmdWrapper]: 416 | for cmd_2q_class in builtins: 417 | self._test_two_qubits_reset_command_1(cmd_2q_class, cmd_reset) 418 | 419 | for cmd_3q_class in (X_GATE,): 420 | self._test_three_qubits_command_1(cmd_3q_class) 421 | self._test_three_qubits_command_2(cmd_3q_class) 422 | self._test_three_qubits_command_3(cmd_3q_class) 423 | self._test_three_qubits_command_4(cmd_3q_class) 424 | self._test_three_qubits_command_5(cmd_3q_class) 425 | self._test_three_qubits_command_6(cmd_3q_class) 426 | self._test_three_qubits_command_7(cmd_3q_class) 427 | self._test_three_qubits_command_8(cmd_3q_class) 428 | 429 | for cmd_reset in [ResetCmd, _MeasurementCmdWrapper]: 430 | for cmd_3q_class in (X_GATE,): 431 | self._test_three_qubits_reset_command_1(cmd_3q_class, cmd_reset) 432 | self._test_three_qubits_reset_command_2(cmd_3q_class, cmd_reset) 433 | self._test_three_qubits_reset_command_3(cmd_3q_class, cmd_reset) 434 | 435 | 436 | if __name__ == '__main__': 437 | unittest.main() 438 | -------------------------------------------------------------------------------- /quasar_qasm.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import List, Set 24 | 25 | from builtin_gates import X_GATE, Y_GATE, Z_GATE, H_GATE, U3_GATE, BuiltinGate 26 | from quasar_formatter import IQAsmFormatter 27 | 28 | 29 | class QASMFormatter(IQAsmFormatter): 30 | GATES_MAPPING = { 31 | X_GATE: ['x', 'cx', 'ccx'], 32 | Y_GATE: ['y', 'cy'], 33 | Z_GATE: ['z', 'cz'], 34 | H_GATE: ['h', 'ch'], 35 | U3_GATE: ['u3', 'cu3'], 36 | } 37 | 38 | def get_headers(self) -> List[str]: 39 | return [ 40 | f'OPENQASM 2.0;', 41 | f'include "qelib1.inc";', 42 | f' ', 43 | f'qreg q[{self.qubits_counter}];', 44 | f'creg c[{self.bits_counter}];', 45 | f' ' 46 | ] 47 | 48 | def get_footers(self) -> List[str]: 49 | return [] 50 | 51 | def gate(self, gate: BuiltinGate, qubit: int, params: List[float], control_qubit_ids: Set[int]) -> str: 52 | operator_name = self._get_for_gate(self.GATES_MAPPING, gate, len(control_qubit_ids)) 53 | if params: 54 | operator_name += '(' + ', '.join(map(str, params)) + ')' 55 | return operator_name + ' ' + ', '.join(map(lambda i: f'q[{i}]', sorted(control_qubit_ids) + [qubit])) + ';' 56 | 57 | def measure(self, qubit: int, bit: int) -> str: 58 | return f'measure q[{qubit}] -> c[{bit}];' 59 | 60 | def reset(self, qubit: int) -> str: 61 | return f'reset q[{qubit}];' 62 | -------------------------------------------------------------------------------- /quasar_qasm_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from qiskit import QuantumCircuit 4 | 5 | from builtin_gates import X_GATE, Z_GATE 6 | from quasar_qasm import QASMFormatter 7 | 8 | 9 | class QasmFormatterTest(unittest.TestCase): 10 | def test_formatter(self) -> None: 11 | f = QASMFormatter() 12 | self.assertEqual(f.gate(X_GATE, 10, [], set()).strip(), 13 | 'x q[10];') 14 | self.assertEqual(f.gate(X_GATE, 10, [], {20}).strip(), 15 | 'cx q[20], q[10];') 16 | self.assertEqual(f.gate(X_GATE, 10, [], {20, 30}).strip(), 17 | 'ccx q[20], q[30], q[10];') 18 | 19 | def test_is_loadable(self) -> None: 20 | f = QASMFormatter() 21 | f.set_qubits_counter(2) 22 | f.set_bits_counter(1) 23 | lines = [ 24 | f.gate(X_GATE, 0, [], set()), 25 | f.gate(Z_GATE, 1, [], {0}) 26 | ] 27 | lines = f.get_headers() + lines + f.get_footers() 28 | code = '\n'.join(lines) 29 | 30 | # Just test if it loads with no exceptions 31 | QuantumCircuit.from_qasm_str(code) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /quasar_qiskit.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import Any, List, Set 24 | 25 | import qiskit 26 | 27 | from builtin_gates import BuiltinGate, X_GATE, Y_GATE, Z_GATE, H_GATE, U3_GATE 28 | from quasar import IQAsmFormatter 29 | 30 | 31 | # 32 | ## 33 | # 34 | 35 | class QiskitFormatter(IQAsmFormatter): 36 | GATES_MAPPING = { 37 | X_GATE: ['x', 'cx', 'ccx'], 38 | Y_GATE: ['y', 'cy'], 39 | Z_GATE: ['z', 'cz'], 40 | H_GATE: ['h', 'ch'], 41 | U3_GATE: ['u3', 'cu3'], 42 | } 43 | 44 | def get_headers(self) -> List[str]: 45 | return [ 46 | f'self.q_register = qiskit.QuantumRegister({self.qubits_counter}, "q_register")', 47 | f'self.c_register = qiskit.ClassicalRegister({self.bits_counter}, "c_register")', 48 | f'self.circuit = qiskit.QuantumCircuit(self.q_register, self.c_register)' 49 | ] 50 | 51 | def get_footers(self) -> List[str]: 52 | return [] 53 | 54 | def gate(self, gate: BuiltinGate, qubit: int, params: List[float], control_qubit_ids: Set[int]) -> str: 55 | method_name = self._get_for_gate(self.GATES_MAPPING, gate, len(control_qubit_ids)) 56 | arguments: List[str] = [] 57 | arguments.extend(map(str, params)) 58 | arguments.extend(map(lambda i: f'self.q_register[{i}]', sorted(control_qubit_ids) + [qubit])) 59 | return f'self.circuit.{method_name}(' + ', '.join(arguments) + ')' 60 | 61 | def measure(self, qubit: int, bit: int) -> str: 62 | return f'self.circuit.measure(self.q_register[{qubit}], self.c_register[{bit}])' 63 | 64 | def reset(self, qubit: int) -> str: 65 | return f'self.reset(self.q_register[{qubit}])' 66 | 67 | # 68 | ## 69 | # 70 | 71 | class QiskitBuilder(QiskitFormatter): 72 | def __init__(self) -> None: 73 | super().__init__() 74 | 75 | # These objects should not be really used, 76 | # the real ones are created in `get_headers`. 77 | self.q_register = qiskit.QuantumRegister(1, 'q_dummy_object') 78 | self.c_register = qiskit.ClassicalRegister(1, 'c_dummy_object') 79 | self.circuit = qiskit.QuantumCircuit(self.q_register, self.c_register) 80 | self._gates_mapping = self._build_gates_mapping() 81 | 82 | def _build_gates_mapping(self): 83 | return { 84 | X_GATE: [self.circuit.x, self.circuit.cx, self.circuit.ccx], 85 | Y_GATE: [self.circuit.y, self.circuit.cy], 86 | Z_GATE: [self.circuit.z, self.circuit.cz], 87 | H_GATE: [self.circuit.h, self.circuit.ch], 88 | U3_GATE: [self.circuit.u3, self.circuit.cu3], 89 | } 90 | 91 | def get_circuit(self): 92 | return self.circuit 93 | 94 | def get_headers(self) -> List[str]: 95 | self.q_register = qiskit.QuantumRegister(self.qubits_counter, 'q_register') 96 | self.c_register = qiskit.ClassicalRegister(self.bits_counter or 1, 'c_register') 97 | self.circuit = qiskit.QuantumCircuit(self.q_register, self.c_register) 98 | self._gates_mapping = self._build_gates_mapping() 99 | 100 | return super().get_headers() 101 | 102 | def get_footers(self) -> List[str]: 103 | return super().get_footers() 104 | 105 | def gate(self, gate: BuiltinGate, qubit: int, params: List[float], control_qubit_ids: Set[int]) -> str: 106 | method = self._get_for_gate(self._gates_mapping, gate, len(control_qubit_ids)) 107 | arguments: List[Any] = [] 108 | arguments.extend(params) 109 | arguments.extend(map(lambda i: self.q_register[i], sorted(control_qubit_ids) + [qubit])) 110 | method(*arguments) 111 | return super().gate(gate, qubit, params, control_qubit_ids) 112 | 113 | def measure(self, qubit: int, bit: int) -> str: 114 | self.circuit.measure(self.q_register[qubit], self.c_register[bit]) 115 | return super().measure(qubit, bit) 116 | 117 | def reset(self, qubit: int) -> str: 118 | self.circuit.reset(self.q_register[qubit]) 119 | return super().reset(qubit) 120 | -------------------------------------------------------------------------------- /quasar_qiskit_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from qiskit.circuit.library.standard_gates import CXGate 4 | 5 | from builtin_gates import X_GATE 6 | from quasar_qiskit import QiskitFormatter, QiskitBuilder 7 | 8 | 9 | class QiskitFormatterTest(unittest.TestCase): 10 | 11 | def test_formatter(self) -> None: 12 | f = QiskitFormatter() 13 | self.assertEqual(f.gate(X_GATE, 10, [], {20}), 14 | 'self.circuit.cx(self.q_register[20], self.q_register[10])') 15 | 16 | def test_builder(self) -> None: 17 | f = QiskitBuilder() 18 | f.set_qubits_counter(3) 19 | f.get_headers() 20 | f.gate(X_GATE, 1, [], {2}) 21 | 22 | cdata = f.get_circuit().data 23 | self.assertEqual(len(cdata), 1) 24 | cmd, qs, cs = cdata[0] 25 | self.assertIsInstance(cmd, CXGate) 26 | self.assertSetEqual({q.index for q in qs}, {1, 2}) 27 | self.assertEqual(len(cs), 0) 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /qutils.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019- Beit, Beit.Tech, Beit.Inc 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | 23 | from typing import Any, List, Union 24 | 25 | from quasar import All, CNot, If, Inv, X, Program, Qubit 26 | 27 | # 28 | ## 29 | # 30 | 31 | def __number_of_digits(value: int, base=10) -> int: 32 | if (value == 0): 33 | return 0 34 | else: 35 | return 1 + __number_of_digits(value // base, base) 36 | 37 | 38 | def _number_of_digits(value: int, base=10) -> int: 39 | return max(1, __number_of_digits(value, base)) 40 | 41 | 42 | def _number_of_bits(value: int) -> int: 43 | return _number_of_digits(value, base=2) 44 | 45 | 46 | def _to_binary_mask(value: int) -> List[int]: 47 | if (value == 0): 48 | return [] 49 | 50 | return _to_binary_mask(value // 2) + [value % 2] 51 | 52 | 53 | def _to_list(obj: Union[Any, List[Any]]) -> List[Any]: 54 | if isinstance(obj, list): 55 | return obj 56 | else: 57 | return _to_list([obj]) 58 | 59 | 60 | def _list_intersection(list1: List[Any], list2: List[Any]) -> List[Any]: 61 | return [x for x in list1 if x in list2] 62 | 63 | 64 | def _list_xor(list1: List[Any], list2: List[Any]) -> List[Any]: 65 | return [x for x in list1 if x not in list2] + [x for x in list2 if x not in list1] 66 | 67 | # 68 | ## 69 | # 70 | 71 | def Set( 72 | qs: List[Qubit], 73 | value: int 74 | ) -> Program: 75 | if (value == 0) or (len(qs) == 0): 76 | return Program() 77 | 78 | if (value % 2 == 1): 79 | return Program(X(qs[-1])) + Set(qs[0: -1], value//2) 80 | else: 81 | return Set(qs[0: -1], value//2) 82 | 83 | 84 | def Inc( 85 | qs: List[Qubit] 86 | ) -> Program: 87 | prgm = Program() 88 | 89 | for i in range(len(qs) - 1): 90 | prgm += If(All(qs[i + 1: ])).Then( 91 | X(qs[i]) 92 | ) 93 | 94 | prgm += X(qs[-1]) 95 | 96 | return prgm 97 | 98 | 99 | def Dec( 100 | qs: List[Qubit] 101 | ) -> Program: 102 | return Program(Inv(Inc(qs))) 103 | 104 | 105 | def Equal( 106 | qs1: Union[Qubit, List[Qubit]], 107 | qs2: Union[Qubit, List[Qubit]] 108 | ) -> Program: 109 | qs1 = _to_list(qs1) 110 | qs2 = _to_list(qs2) 111 | 112 | prgm = Program() 113 | 114 | for (q1, q2) in zip(qs1, qs2): 115 | prgm += X(q1) 116 | prgm += CNot(q1, q2) 117 | prgm += X(q1) 118 | 119 | return prgm 120 | --------------------------------------------------------------------------------