├── setup.cfg ├── qsy ├── backends │ ├── __init__.py │ ├── backend.py │ ├── statevector.py │ └── chp.py ├── gates │ ├── __init__.py │ ├── clifford.py │ ├── gate.py │ ├── pauli.py │ └── non_clifford.py ├── error.py ├── __init__.py ├── util.py ├── classical_register.py ├── register.py └── quantum_register.py ├── examples ├── qsyasm │ ├── bell.qs │ ├── superdense_coding.qs │ ├── teleportation.qs │ ├── grover.qs │ ├── 750_qubits.qs │ ├── stean_encode.qs │ └── qpe.qs └── qsy │ ├── bell.py │ └── efficient_stabilizer_simulation.py ├── qsyasm ├── error.py ├── log.py ├── env.py ├── interpreter │ ├── lexer.py │ ├── parser.py │ └── parsetab.py ├── cli.py ├── instruction.py └── program.py ├── .gitignore ├── LICENSE ├── setup.py └── README.md /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file=README.md 3 | license-file=LICENSE 4 | -------------------------------------------------------------------------------- /qsy/backends/__init__.py: -------------------------------------------------------------------------------- 1 | from .statevector import StatevectorBackend 2 | from .chp import CHPBackend 3 | -------------------------------------------------------------------------------- /qsy/gates/__init__.py: -------------------------------------------------------------------------------- 1 | from .pauli import * 2 | from .clifford import * 3 | from .non_clifford import * 4 | -------------------------------------------------------------------------------- /qsy/error.py: -------------------------------------------------------------------------------- 1 | class RegisterIndexError(IndexError): 2 | pass 3 | 4 | 5 | class InvalidRegisterError(RuntimeError): 6 | pass 7 | -------------------------------------------------------------------------------- /examples/qsyasm/bell.qs: -------------------------------------------------------------------------------- 1 | qreg[2] q 2 | creg[2] c 3 | 4 | h q[0] 5 | cx q[0], q[1] 6 | 7 | meas q, c 8 | ; or: 9 | ; meas q[0], c[0] 10 | ; meas q[1], c[1] 11 | -------------------------------------------------------------------------------- /qsy/__init__.py: -------------------------------------------------------------------------------- 1 | from .quantum_register import QuantumRegister 2 | from .classical_register import ClassicalRegister 3 | import qsy.gates as gates 4 | 5 | 6 | __version__ = '0.4.4' 7 | -------------------------------------------------------------------------------- /examples/qsy/bell.py: -------------------------------------------------------------------------------- 1 | from qsy import QuantumRegister, gates 2 | 3 | qr = QuantumRegister(2) 4 | qr.apply_gate(gates.H, 0) 5 | qr.apply_gate(gates.CX, 0, 1) 6 | 7 | print(qr.to_dirac()) 8 | -------------------------------------------------------------------------------- /examples/qsyasm/superdense_coding.qs: -------------------------------------------------------------------------------- 1 | qreg[2] q 2 | creg[2] c 3 | 4 | h q[0] 5 | cx q[0], q[1] 6 | 7 | ; encode 11 8 | x q[1] 9 | z q[1] 10 | 11 | ; encode 01 12 | ; x q[1] 13 | 14 | ; encode 10 15 | ; z q[1] 16 | 17 | cx q[0], q[1] 18 | h q[0] 19 | 20 | meas q, c 21 | -------------------------------------------------------------------------------- /examples/qsyasm/teleportation.qs: -------------------------------------------------------------------------------- 1 | qreg[3] q 2 | 3 | ; prepare state to be teleported: |+i> 4 | h q[0] 5 | s q[0] 6 | 7 | ; create EPR pair 8 | h q[1] 9 | cx q[1], q[2] 10 | 11 | cx q[0], q[1] 12 | h q[0] 13 | 14 | meas q[0] 15 | cz q[0], q[2] 16 | 17 | meas q[1] 18 | cx q[1], q[2] 19 | -------------------------------------------------------------------------------- /qsyasm/error.py: -------------------------------------------------------------------------------- 1 | class ParseError(Exception): 2 | def __init__(self, msg, lexpos, lineno): 3 | super(ParseError, self).__init__(msg) 4 | self.msg = msg 5 | self.lexpos = lexpos 6 | self.lineno = lineno 7 | 8 | 9 | class QsyASMError(RuntimeError): 10 | pass 11 | -------------------------------------------------------------------------------- /examples/qsyasm/grover.qs: -------------------------------------------------------------------------------- 1 | qreg[3] q 2 | creg[2] c 3 | 4 | ; prep 5 | h q[0] 6 | h q[1] 7 | 8 | x q[2] 9 | h q[2] 10 | 11 | ; oracle 12 | ccx q[0], q[1], q[2] 13 | 14 | ; post-process 15 | h q[0] 16 | h q[1] 17 | 18 | x q[0] 19 | x q[1] 20 | 21 | cz q[0], q[1] 22 | 23 | x q[0] 24 | x q[1] 25 | 26 | h q[0] 27 | h q[1] 28 | 29 | meas q[0], c[0] 30 | meas q[1], c[1] 31 | -------------------------------------------------------------------------------- /examples/qsyasm/750_qubits.qs: -------------------------------------------------------------------------------- 1 | ; run this using the CHP back-end 2 | 3 | qreg[750] q 4 | creg[750] c 5 | 6 | h q[50] 7 | h q[75] 8 | h q[100] 9 | 10 | cx q[50], q[51] 11 | cx q[50], q[52] 12 | cx q[50], q[53] 13 | cx q[50], q[54] 14 | cx q[50], q[55] 15 | cx q[50], q[56] 16 | cx q[50], q[57] 17 | cx q[50], q[58] 18 | cx q[50], q[59] 19 | cx q[50], q[60] 20 | 21 | meas q, c 22 | -------------------------------------------------------------------------------- /examples/qsyasm/stean_encode.qs: -------------------------------------------------------------------------------- 1 | qreg[7] q 2 | 3 | ; uncomment to encode |1>, otherwise encode |0> 4 | ; x q[0] 5 | 6 | cx q[0], q[1] 7 | cx q[0], q[2] 8 | 9 | h q[4] 10 | h q[5] 11 | h q[6] 12 | 13 | cx q[4], q[3] 14 | cx q[4], q[2] 15 | cx q[4], q[1] 16 | 17 | cx q[5], q[3] 18 | cx q[5], q[2] 19 | cx q[5], q[0] 20 | 21 | cx q[6], q[3] 22 | cx q[6], q[1] 23 | cx q[6], q[0] 24 | -------------------------------------------------------------------------------- /examples/qsy/efficient_stabilizer_simulation.py: -------------------------------------------------------------------------------- 1 | from qsy import QuantumRegister, gates 2 | from qsy.backends import CHPBackend 3 | 4 | qr = QuantumRegister(500, backend=CHPBackend) 5 | 6 | for i in range(250): 7 | qr.apply_gate(gates.H, i) 8 | 9 | for i in range(250): 10 | qr.apply_gate(gates.CX, 0, i + 250) 11 | 12 | measurement = qr.measure_all() 13 | print(measurement) 14 | -------------------------------------------------------------------------------- /qsy/util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def format_complex(c): 5 | fmt = '{:+.5f}'.format(c.real).rstrip('0').rstrip('.') 6 | 7 | if not np.isclose(c.imag, 0): 8 | imag_fmt = '{:+.5f}'.format(c.imag).rstrip('0').rstrip('.') + 'i' 9 | if np.isclose(c.real, 0.0): 10 | fmt = imag_fmt 11 | else: 12 | fmt += imag_fmt 13 | 14 | return fmt 15 | -------------------------------------------------------------------------------- /qsy/gates/clifford.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .gate import C, Gate 4 | from .pauli import X, Y, Z 5 | 6 | H_MATRIX = 1/np.sqrt(2) * np.array([[1, 1], 7 | [1, -1]]) 8 | S_MATRIX = np.array([[1, 0], 9 | [0, 1j]]) 10 | 11 | H = Gate('H', H_MATRIX, H_MATRIX, 1) 12 | 13 | S = Gate('S', S_MATRIX, S_MATRIX.conj().T, 1) 14 | 15 | CX = C(X) 16 | CY = C(Y) 17 | CZ = C(Z) 18 | -------------------------------------------------------------------------------- /qsy/gates/gate.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | Gate = collections.namedtuple( 4 | 'Gate', ['name', 'matrix', 'adjoint_matrix', 'arity'] 5 | ) 6 | 7 | 8 | def C(gate): 9 | """Create a controlled-U gate.""" 10 | return Gate('C{}'.format(gate.name), gate.matrix, gate.adjoint_matrix, 2) 11 | 12 | 13 | def CC(gate): 14 | """Create a controlled-controlled-U gate.""" 15 | return Gate('CC{}'.format(gate.name), gate.matrix, gate.adjoint_matrix, 3) 16 | -------------------------------------------------------------------------------- /qsy/classical_register.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from .register import Register 4 | 5 | 6 | class ClassicalRegister(Register): 7 | instance_counter = itertools.count() 8 | prefix = 'c' 9 | 10 | def __init__(self, size, name=None): 11 | super().__init__(size, name) 12 | self.state = [0] * size 13 | 14 | def set_state(self, state): 15 | self.state = state 16 | 17 | def __getitem__(self, index): 18 | return self.state[index] 19 | 20 | def __setitem__(self, index, value): 21 | self.state[index] = value 22 | -------------------------------------------------------------------------------- /qsy/gates/pauli.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .gate import Gate 4 | 5 | PAULI_I_MATRIX = np.array([[1, 0], 6 | [0, 1]]) 7 | PAULI_X_MATRIX = np.array([[0, 1], 8 | [1, 0]]) 9 | PAULI_Y_MATRIX = np.array([[0, -1j], 10 | [1j, 0]]) 11 | PAULI_Z_MATRIX = np.array([[1, 0], 12 | [0, -1]]) 13 | 14 | I = Gate('I', PAULI_I_MATRIX, PAULI_I_MATRIX, 1) 15 | 16 | X = Gate('X', PAULI_X_MATRIX, PAULI_X_MATRIX, 1) 17 | 18 | Y = Gate('Y', PAULI_Y_MATRIX, PAULI_Y_MATRIX, 1) 19 | 20 | Z = Gate('Z', PAULI_Z_MATRIX, PAULI_Z_MATRIX, 1) 21 | -------------------------------------------------------------------------------- /qsy/register.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from .error import InvalidRegisterError 4 | 5 | 6 | class Register: 7 | instance_counter = itertools.count() 8 | prefix = 'r' 9 | 10 | def __init__(self, size, name=None): 11 | if size <= 0 or not isinstance(size, int): 12 | raise InvalidRegisterError('Invalid register size "{}"'.format(size)) 13 | 14 | self.size = size 15 | 16 | if name is None: 17 | name = '{}{}'.format(self.prefix, next(self.instance_counter)) 18 | 19 | self.name = name 20 | 21 | def __len__(self): 22 | return self.size 23 | 24 | def __repr__(self): 25 | return '{}[{}]'.format(self.name, self.size) 26 | -------------------------------------------------------------------------------- /qsyasm/log.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | NO_COLOR = '\33[m' 4 | BOLD = '\033[1m' 5 | RED, GREEN, ORANGE = ('\33[{}m'.format(i) for i in range(31, 34)) 6 | 7 | 8 | def error_fmt(message): 9 | return '{}{}error{}: {}'.format(BOLD, RED, NO_COLOR, message) 10 | 11 | 12 | def warning_fmt(message): 13 | return '{}{}warning{}: {}'.format(BOLD, ORANGE, NO_COLOR, message) 14 | 15 | 16 | def info_fmt(message): 17 | return '{}{}info{}: {}'.format(BOLD, GREEN, NO_COLOR, message) 18 | 19 | 20 | def print_error(message): 21 | print(error_fmt(message), file=sys.stderr) 22 | 23 | 24 | def print_warning(message): 25 | print(warning_fmt(message), file=sys.stderr) 26 | 27 | 28 | def print_info(message): 29 | print(info_fmt(message), file=sys.stderr) 30 | -------------------------------------------------------------------------------- /qsyasm/env.py: -------------------------------------------------------------------------------- 1 | from qsy import ClassicalRegister, QuantumRegister 2 | from qsy.error import RegisterIndexError 3 | 4 | from .error import QsyASMError 5 | 6 | 7 | class Env: 8 | def __init__(self): 9 | self.qrs = {} 10 | self.crs = {} 11 | 12 | def qr(self, name): 13 | if name not in self.qrs: 14 | raise RegisterIndexError('Undefined quantum register "{}"'.format(name)) 15 | 16 | return self.qrs[name] 17 | 18 | def cr(self, name): 19 | if name not in self.crs: 20 | raise RegisterIndexError('Undefined classical register "{}"'.format(name)) 21 | 22 | return self.crs[name] 23 | 24 | def create_qr(self, name, size, backend): 25 | self.qrs[name] = QuantumRegister(size, name, backend=backend) 26 | 27 | def create_cr(self, name, size): 28 | self.crs[name] = ClassicalRegister(size, name) 29 | -------------------------------------------------------------------------------- /examples/qsyasm/qpe.qs: -------------------------------------------------------------------------------- 1 | qreg[5] q 2 | creg[4] c 3 | 4 | h q[0] 5 | h q[1] 6 | h q[2] 7 | h q[3] 8 | 9 | x q[4] 10 | 11 | ; estimate phase of phi = 12(2π)/16 12 | crz(2**0 * (12*2*pi)/16) q[3], q[4] 13 | crz(2**1 * (12*2*pi)/16) q[2], q[4] 14 | crz(2**2 * (12*2*pi)/16) q[1], q[4] 15 | crz(2**3 * (12*2*pi)/16) q[0], q[4] 16 | 17 | ; inverse quantum fourier transform 18 | h q[0] 19 | 20 | adj crz(pi/2**1) q[1], q[0] 21 | h q[1] 22 | 23 | adj crz(pi/2**1) q[2], q[1] 24 | adj crz(pi/2**2) q[2], q[0] 25 | h q[2] 26 | 27 | adj crz(pi/2**1) q[3], q[2] 28 | adj crz(pi/2**2) q[3], q[1] 29 | adj crz(pi/2**3) q[3], q[0] 30 | h q[3] 31 | 32 | ; swap to get correct result from iqft 33 | cx q[3], q[0] 34 | cx q[0], q[3] 35 | cx q[3], q[0] 36 | 37 | cx q[2], q[1] 38 | cx q[1], q[2] 39 | cx q[2], q[1] 40 | 41 | ; measure to get phase approximation 42 | meas q[0], c[0] 43 | meas q[1], c[1] 44 | meas q[2], c[2] 45 | meas q[3], c[3] 46 | -------------------------------------------------------------------------------- /qsy/backends/backend.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from qsy.error import RegisterIndexError 4 | 5 | 6 | class Backend(abc.ABC): 7 | @abc.abstractmethod 8 | def apply_gate(self, gate, *params, adjoint=False): 9 | raise NotImplementedError() 10 | 11 | @abc.abstractmethod 12 | def measure_all(self): 13 | raise NotImplementedError() 14 | 15 | @abc.abstractmethod 16 | def measure(self, target): 17 | raise NotImplementedError() 18 | 19 | @abc.abstractmethod 20 | def yield_state(self): 21 | raise NotImplementedError() 22 | 23 | @abc.abstractmethod 24 | def to_dirac(self): 25 | raise NotImplementedError() 26 | 27 | def _check_in_range(self, target): 28 | if target < 0 or target >= self.size: 29 | raise RegisterIndexError( 30 | 'Can\'t access {}[{}]: register index out of range (register size {})'.format( 31 | self.name, target, self.size 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # ply parser debug 61 | parser.out 62 | -------------------------------------------------------------------------------- /qsy/gates/non_clifford.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .gate import CC, C, Gate 4 | from .pauli import X 5 | 6 | T_MATRIX = np.array([[1, 0], 7 | [0, np.exp(1j * np.pi / 4)]]) 8 | 9 | T = Gate('T', T_MATRIX, T_MATRIX.conj().T, 1) 10 | 11 | 12 | def Rx(angle): 13 | matrix = np.array([[np.cos(angle/2), -1j * np.sin(angle/2)], 14 | [-1j * np.sin(angle/2), np.cos(angle/2)]]) 15 | return Gate('Rx', matrix, matrix.conj().T, 1) 16 | 17 | 18 | def Ry(angle): 19 | matrix = np.array([[np.cos(angle/2), -np.sin(angle/2)], 20 | [np.sin(angle/2), np.cos(angle/2)]]) 21 | return Gate('Ry', matrix, matrix.conj().T, 1) 22 | 23 | 24 | def Rz(angle): 25 | matrix = np.array([[1, 0], 26 | [0, np.exp(1j * angle)]]) 27 | return Gate('Rz', matrix, matrix.conj().T, 1) 28 | 29 | 30 | def CRx(angle): 31 | return C(Rx(angle)) 32 | 33 | 34 | def CRy(angle): 35 | return C(Ry(angle)) 36 | 37 | 38 | def CRz(angle): 39 | return C(Rz(angle)) 40 | 41 | 42 | CCX = CC(X) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Steven Oud 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /qsy/quantum_register.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from .backends import StatevectorBackend 4 | from .register import Register 5 | 6 | 7 | class QuantumRegister(Register): 8 | instance_counter = itertools.count() 9 | prefix = 'q' 10 | 11 | def __init__(self, size, name=None, backend=StatevectorBackend): 12 | super().__init__(size, name) 13 | self.backend = backend(self.size, self.name) 14 | 15 | def apply_gate(self, gate, *params, adjoint=False): 16 | if len(params) != gate.arity: 17 | raise Exception( 18 | 'Gate {} expects {} parameters, got {}'.format( 19 | gate.name, gate.arity, len(params) 20 | ) 21 | ) 22 | 23 | self.backend.apply_gate(gate, *params, adjoint=adjoint) 24 | 25 | def measure_all(self): 26 | return self.backend.measure_all() 27 | 28 | def measure(self, target): 29 | return self.backend.measure(target) 30 | 31 | def yield_state(self): 32 | return self.backend.yield_state() 33 | 34 | def to_dirac(self): 35 | return self.backend.to_dirac() 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from setuptools import find_packages, setup 4 | 5 | with open('./qsy/__init__.py') as f: 6 | version, = re.findall('__version__ = \'(.*)\'', f.read()) 7 | 8 | with open('README.md', 'r') as f: 9 | long_description = f.read() 10 | 11 | setup( 12 | name='qsy', 13 | version=version, 14 | packages=find_packages(), 15 | author='Steven Oud', 16 | author_email='soud@pm.me', 17 | description='A quantum computer state vector/stabilizer circuit simulator and assembly language', 18 | long_description=long_description, 19 | long_description_content_type='text/markdown', 20 | url='https://github.com/soudy/qsy', 21 | license='MIT', 22 | install_requires=['numpy>=1.13', 'ply>=3'], 23 | download_url='https://github.com/soudy/qsy/archive/v{}.tar.gz'.format(version), 24 | keywords=['quantum', 'computing', 'simulator', 'stabilizer', 'circuit', 25 | 'assembly', 'chp', 'statevector'], 26 | entry_points={ 27 | 'console_scripts': ['qsyasm=qsyasm.cli:main'] 28 | }, 29 | classifiers=[ 30 | 'Environment :: Console', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python :: 3 :: Only', 34 | 'Topic :: Software Development :: Assemblers' 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /qsyasm/interpreter/lexer.py: -------------------------------------------------------------------------------- 1 | import ply.lex as lex 2 | 3 | from qsyasm.error import ParseError 4 | 5 | 6 | class QsyASMLexer: 7 | tokens = ( 8 | 'INTEGER', 9 | 'FLOAT', 10 | 'IDENT', 11 | 'COMMA', 12 | 'LBRACKET', 13 | 'RBRACKET', 14 | 'LPAREN', 15 | 'RPAREN', 16 | 'PLUS', 17 | 'MIN', 18 | 'DIV', 19 | 'POW', 20 | 'MUL', 21 | 'NEWLINE', 22 | 'ADJ' 23 | ) 24 | 25 | reserved = { 26 | 'adj': 'ADJ' 27 | } 28 | 29 | t_COMMA = r',' 30 | t_LBRACKET = r'\[' 31 | t_RBRACKET = r'\]' 32 | t_LPAREN = r'\(' 33 | t_RPAREN = r'\)' 34 | t_PLUS = r'\+' 35 | t_MIN = r'-' 36 | t_DIV = r'/' 37 | t_POW = r'\*\*' 38 | t_MUL = r'\*' 39 | 40 | t_ignore = '\t\r ' 41 | t_ignore_COMMENT = r';.*' 42 | 43 | def __init__(self, **kwargs): 44 | self.lexer = lex.lex(module=self, **kwargs) 45 | 46 | def t_IDENT(self, t): 47 | r'[a-zA-Z_][a-zA-Z0-9_]*' 48 | t.type = self.reserved.get(t.value, 'IDENT') 49 | return t 50 | 51 | def t_INTEGER(self, t): 52 | r'\d+' 53 | t.value = int(t.value) 54 | return t 55 | 56 | def t_FLOAT(self, t): 57 | r'\d+\.\d+' 58 | t.value = float(t.value) 59 | return t 60 | 61 | def t_NEWLINE(self, t): 62 | r'\n+' 63 | t.lexer.lineno += t.value.count('\n') 64 | return t 65 | 66 | def t_error(self, t): 67 | raise ParseError('Unknown token "{}"'.format(t.value[0]), t) 68 | 69 | 70 | lexer = QsyASMLexer() 71 | -------------------------------------------------------------------------------- /qsyasm/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | from qsy import __version__ 5 | 6 | from .error import QsyASMError 7 | from .log import print_error 8 | from .program import QsyASMProgram 9 | 10 | 11 | def main(): 12 | argparser = argparse.ArgumentParser(description='qsyasm assembly runner') 13 | 14 | argparser.add_argument('filename', type=str, help='qsyasm file to execute') 15 | argparser.add_argument('-V', '--version', action='version', 16 | version='%(prog)s v' + __version__) 17 | argparser.add_argument('-v', '--verbose', action='store_true', 18 | help='verbose output') 19 | argparser.add_argument('-t', '--time', action='store_true', 20 | help='time program execution') 21 | argparser.add_argument('-b', '--backend', choices=('chp', 'statevector'), 22 | default=None, metavar='B', 23 | help='simulator back-end to use: chp or statevector (default: statevector)') 24 | argparser.add_argument('-s', '--shots', type=int, default=1, 25 | help='amount of shots to run') 26 | argparser.add_argument('--ignore-print-warning', action='store_true', 27 | help='ignore register too large to print warning') 28 | argparser.add_argument('--skip-zero-amplitudes', action='store_true', 29 | help='don\'t print states with an amplitude of 0') 30 | 31 | args = vars(argparser.parse_args()) 32 | 33 | try: 34 | p = QsyASMProgram(args) 35 | p.run() 36 | except QsyASMError as e: 37 | print_error(e) 38 | sys.exit(-1) 39 | -------------------------------------------------------------------------------- /qsyasm/instruction.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum, auto, unique 2 | 3 | from .error import QsyASMError 4 | 5 | 6 | @unique 7 | class Operation(IntEnum): 8 | # Gates 9 | GATES_START = auto() 10 | I = auto() 11 | X = auto() 12 | Y = auto() 13 | Z = auto() 14 | 15 | H = auto() 16 | S = auto() 17 | CX = auto() 18 | CY = auto() 19 | CZ = auto() 20 | 21 | T = auto() 22 | RX = auto() 23 | RY = auto() 24 | RZ = auto() 25 | CRX = auto() 26 | CRY = auto() 27 | CRZ = auto() 28 | CCX = auto() 29 | GATES_END = auto() 30 | 31 | # Registers 32 | QR = auto() 33 | CR = auto() 34 | 35 | MEASURE = auto() 36 | 37 | ERROR = auto() 38 | 39 | 40 | operations = { 41 | 'i': Operation.I, 42 | 'x': Operation.X, 43 | 'y': Operation.Y, 44 | 'z': Operation.Z, 45 | 46 | 'h': Operation.H, 47 | 's': Operation.S, 48 | 'cx': Operation.CX, 49 | 'cz': Operation.CZ, 50 | 51 | 't': Operation.T, 52 | 'rx': Operation.RX, 53 | 'ry': Operation.RY, 54 | 'rz': Operation.RZ, 55 | 'crx': Operation.CRX, 56 | 'cry': Operation.CRY, 57 | 'crz': Operation.CRZ, 58 | 'ccx': Operation.CCX, 59 | 60 | 'qreg': Operation.QR, 61 | 'creg': Operation.CR, 62 | 63 | 'meas': Operation.MEASURE, 64 | 65 | 'error': Operation.ERROR 66 | } 67 | 68 | 69 | class Instruction: 70 | def __init__(self, op, args, lineno, lexpos): 71 | self.op = op 72 | self.op_name = op 73 | self.args = args 74 | self.lineno = lineno 75 | self.lexpos = lexpos 76 | 77 | self.type = self._get_op_type() 78 | 79 | # only applicable for gates 80 | self.adjoint = False 81 | 82 | def is_gate(self): 83 | return self.type > Operation.GATES_START and self.type < Operation.GATES_END 84 | 85 | def toggle_adjoint(self): 86 | self.adjoint = not self.adjoint 87 | 88 | def _get_op_type(self): 89 | self.op_name = self.op 90 | 91 | if isinstance(self.op, tuple): 92 | # parameterized instruction like qreg[n] or c(x) 93 | self.op_name, _ = self.op 94 | 95 | if self.op_name not in operations: 96 | return Operation.ERROR 97 | 98 | return operations[self.op_name] 99 | 100 | def __repr__(self): 101 | return '{}<{} {}>'.format(self.__class__.__name__, self.op, self.args) 102 | -------------------------------------------------------------------------------- /qsyasm/interpreter/parser.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import numpy as np 4 | import ply.yacc as yacc 5 | 6 | from qsyasm.error import ParseError 7 | from qsyasm.instruction import Instruction 8 | 9 | from .lexer import QsyASMLexer 10 | 11 | 12 | class QsyASMParser: 13 | tokens = QsyASMLexer.tokens 14 | precedence = ( 15 | ('left', 'PLUS', 'MIN'), 16 | ('left', 'MUL', 'DIV'), 17 | ('left', 'POW'), 18 | ('right', 'UMIN') 19 | ) 20 | variables = { 21 | 'pi': np.pi 22 | } 23 | 24 | def __init__(self, **kwargs): 25 | self.parser = yacc.yacc(module=self, **kwargs) 26 | 27 | def p_program(self, p): 28 | '''program : program instruction 29 | | instruction''' 30 | if len(p) == 2 and p[1]: 31 | p[0] = [p[1]] 32 | elif len(p) == 3: 33 | p[0] = p[1] 34 | 35 | if not p[0]: 36 | p[0] = [] 37 | 38 | if p[2]: 39 | p[0] += [p[2]] 40 | 41 | def p_instruction(self, p): 42 | '''instruction : normal_instruction 43 | | adjoint_instruction''' 44 | p[0] = p[1] 45 | 46 | def p_normal_instruction(self, p): 47 | '''normal_instruction : term argument_list 48 | | param_term argument_list''' 49 | p[0] = Instruction(p[1], p[2], p.lineno(0), p.lexpos(0)) 50 | 51 | def p_adjoint_instruction(self, p): 52 | 'adjoint_instruction : ADJ normal_instruction' 53 | instr = p[2] 54 | 55 | if not instr.is_gate(): 56 | raise ParseError('Invalid operation "{}" for adjoint'.format(instr.op_name), 57 | p.lexpos(2), p.lineno(2)) 58 | 59 | instr.toggle_adjoint() 60 | p[0] = instr 61 | 62 | def p_instruction_newline(self, p): 63 | 'instruction : NEWLINE' 64 | pass 65 | 66 | def p_term(self, p): 67 | '''term : IDENT 68 | | INTEGER 69 | | lookup''' 70 | p[0] = p[1] 71 | 72 | def p_lookup(self, p): 73 | 'lookup : IDENT LBRACKET INTEGER RBRACKET' 74 | p[0] = (p[1], p[3]) 75 | 76 | def p_param_term(self, p): 77 | 'param_term : IDENT LPAREN expression RPAREN' 78 | p[0] = (p[1], p[3]) 79 | 80 | def p_argument_list(self, p): 81 | 'argument_list : term' 82 | p[0] = [p[1]] 83 | 84 | def p_argument_list_args(self, p): 85 | 'argument_list : term COMMA argument_list' 86 | p[0] = [p[1]] 87 | p[0] += p[3] 88 | 89 | def p_expression_bin_op(self, p): 90 | '''expression : expression PLUS expression 91 | | expression MIN expression 92 | | expression DIV expression 93 | | expression POW expression 94 | | expression MUL expression''' 95 | if p[2] == '+': 96 | p[0] = p[1] + p[3] 97 | elif p[2] == '-': 98 | p[0] = p[1] - p[3] 99 | elif p[2] == '/': 100 | if p[3] == 0.0: 101 | raise ParseError('Division by zero', p.lexpos(2), p.lineno(2)) 102 | p[0] = p[1] / p[3] 103 | elif p[2] == '**': 104 | p[0] = p[1] ** p[3] 105 | elif p[2] == '*': 106 | p[0] = p[1] * p[3] 107 | 108 | def p_expression_group(self, p): 109 | 'expression : LPAREN expression RPAREN' 110 | p[0] = p[2] 111 | 112 | def p_expression_integer(self, p): 113 | 'expression : INTEGER' 114 | p[0] = p[1] 115 | 116 | def p_expression_float(self, p): 117 | 'expression : FLOAT' 118 | p[0] = p[1] 119 | 120 | def p_expression_unary_min(self, p): 121 | 'expression : MIN expression %prec UMIN' 122 | p[0] = -p[2] 123 | 124 | def p_expression_ident(self, p): 125 | 'expression : IDENT' 126 | if p[1] not in self.variables: 127 | raise ParseError( 128 | 'Undefined variable "{}"'.format(p[1]), 129 | p.lexpos(1), p.lineno(1) 130 | ) 131 | 132 | p[0] = self.variables[p[1]] 133 | 134 | def p_error(self, p): 135 | raise ParseError('Unexpected "{}"'.format(p.type), p.lexpos, p.lineno) 136 | 137 | def parse(self, s): 138 | return self.parser.parse(s, debug=False, tracking=True) 139 | -------------------------------------------------------------------------------- /qsy/backends/statevector.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import itertools 3 | 4 | import numpy as np 5 | import numpy.linalg as la 6 | 7 | from qsy import gates 8 | from qsy.util import format_complex 9 | 10 | from .backend import Backend 11 | 12 | 13 | class StatevectorBackend(Backend): 14 | ''' 15 | StatevectorBackend uses a vector to describe the state of the system. 16 | To evolve the system, vector matrix multiplication is used. It can simulate 17 | all supported clifford and non-clifford gates. 18 | ''' 19 | 20 | def __init__(self, size, name): 21 | self.name = name 22 | self.size = size 23 | self.state_size = 2**size 24 | self.state = np.zeros(self.state_size) 25 | self.state[0] = 1 # Initialize to zero state 26 | 27 | def apply_gate(self, gate, *params, adjoint=False): 28 | if gate.arity == 1: 29 | target = params[0] 30 | self._apply_single_qubit_gate(gate, target, adjoint) 31 | else: 32 | *controls, target = params 33 | self._apply_controlled_gate(gate, controls, target, adjoint) 34 | 35 | def measure_all(self): 36 | probabilities = np.abs(self.state)**2 37 | 38 | measured = np.random.choice(len(probabilities), p=probabilities) 39 | self.state[:] = 0 40 | self.state[measured] = 1 41 | 42 | binary_measurement = format(measured, '0{}b'.format(self.size)) 43 | return [int(x) for x in binary_measurement] 44 | 45 | def measure(self, target): 46 | step_size = self.state_size // 2**(target+1) 47 | n_steps = (self.state.size // step_size) // 2 48 | 49 | # Create masks to extract amplitudes where target qubit is zero and one. 50 | zeros_mask = np.tile( 51 | np.concatenate((np.ones(step_size), np.zeros(step_size))), n_steps 52 | ) 53 | ones_mask = np.tile( 54 | np.concatenate((np.zeros(step_size), np.ones(step_size))), n_steps 55 | ) 56 | 57 | zero_amplitudes = self.state * zeros_mask 58 | one_amplitudes = self.state * ones_mask 59 | probabilities = np.abs(zero_amplitudes + one_amplitudes)**2 60 | 61 | measured = np.random.choice(len(probabilities), p=probabilities) 62 | measured_value = 1 if one_amplitudes[measured] != 0.0 else 0 63 | 64 | if measured_value == 1: 65 | for i in zero_amplitudes.nonzero(): 66 | self.state[i] = 0 67 | else: 68 | for i in one_amplitudes.nonzero(): 69 | self.state[i] = 0 70 | 71 | self._normalize() 72 | 73 | return measured_value 74 | 75 | def yield_state(self): 76 | for i, amplitude in np.ndenumerate(self.state): 77 | yield i[0], amplitude 78 | 79 | def to_dirac(self): 80 | return ' '.join('{}|{:0{n:d}b}>'.format(format_complex(a), i, n=self.size) 81 | for a, i in zip(self.state, itertools.count()) 82 | if not np.isclose(a, 0.0)) 83 | 84 | def _apply_single_qubit_gate(self, gate, target, adjoint): 85 | self._check_in_range(target) 86 | 87 | operation = gate.adjoint_matrix if adjoint else gate.matrix 88 | 89 | transformation = [gates.I.matrix] * self.size 90 | transformation[target] = operation 91 | 92 | transformation_matrix = functools.reduce(np.kron, transformation) 93 | 94 | self._transform(transformation_matrix) 95 | 96 | def _apply_controlled_gate(self, gate, controls, target, adjoint): 97 | self._check_in_range(target) 98 | 99 | for control in controls: 100 | self._check_in_range(control) 101 | 102 | operation = gate.adjoint_matrix if adjoint else gate.matrix 103 | control_matrix = np.array([[float('nan'), 0], 104 | [0, 1]]) 105 | transformation = [] 106 | 107 | for i in range(self.size): 108 | if i in controls: 109 | transformation.append(control_matrix) 110 | elif i == target: 111 | transformation.append(operation) 112 | else: 113 | transformation.append(gates.I.matrix) 114 | 115 | transformation_matrix = functools.reduce(np.kron, transformation) 116 | 117 | for (i, j), value in np.ndenumerate(transformation_matrix): 118 | if np.isnan(value): 119 | if i == j: 120 | transformation_matrix[i, j] = 1 121 | else: 122 | transformation_matrix[i, j] = 0 123 | 124 | self._transform(transformation_matrix) 125 | 126 | def _transform(self, operation): 127 | self.state = np.dot(self.state, operation) 128 | 129 | def _normalize(self): 130 | self.state = self.state / la.norm(self.state) 131 | -------------------------------------------------------------------------------- /qsyasm/interpreter/parsetab.py: -------------------------------------------------------------------------------- 1 | 2 | # parsetab.py 3 | # This file is automatically generated. Do not edit. 4 | # pylint: disable=W,C,R 5 | _tabversion = '3.10' 6 | 7 | _lr_method = 'LALR' 8 | 9 | _lr_signature = 'leftPLUSMINleftMULDIVleftPOWrightUMINADJ COMMA DIV FLOAT IDENT INTEGER LBRACKET LPAREN MIN MUL NEWLINE PLUS POW RBRACKET RPARENprogram : program instruction\n | instructioninstruction : normal_instruction\n | adjoint_instructionnormal_instruction : term argument_list\n | param_term argument_listadjoint_instruction : ADJ normal_instructioninstruction : NEWLINEterm : IDENT\n | INTEGER\n | lookuplookup : IDENT LBRACKET INTEGER RBRACKETparam_term : IDENT LPAREN expression RPARENargument_list : termargument_list : term COMMA argument_listexpression : expression PLUS expression\n | expression MIN expression\n | expression DIV expression\n | expression POW expression\n | expression MUL expressionexpression : LPAREN expression RPARENexpression : INTEGERexpression : FLOATexpression : MIN expression %prec UMINexpression : IDENT' 10 | 11 | _lr_action_items = {'NEWLINE':([0,1,2,3,4,5,10,11,12,13,14,15,16,17,28,37,],[5,5,-2,-3,-4,-8,-10,-11,-1,-14,-5,-9,-6,-7,-15,-12,]),'ADJ':([0,1,2,3,4,5,10,11,12,13,14,15,16,17,28,37,],[8,8,-2,-3,-4,-8,-10,-11,-1,-14,-5,-9,-6,-7,-15,-12,]),'IDENT':([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,24,28,30,31,32,33,34,35,37,],[9,9,-2,-3,-4,-8,15,15,9,-9,-10,-11,-1,-14,-5,-9,-6,-7,21,15,21,21,-15,-13,21,21,21,21,21,-12,]),'INTEGER':([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,24,28,30,31,32,33,34,35,37,],[10,10,-2,-3,-4,-8,10,10,10,-9,-10,-11,-1,-14,-5,-9,-6,-7,25,27,10,25,25,-15,-13,25,25,25,25,25,-12,]),'$end':([1,2,3,4,5,10,11,12,13,14,15,16,17,28,37,],[0,-2,-3,-4,-8,-10,-11,-1,-14,-5,-9,-6,-7,-15,-12,]),'LPAREN':([9,18,22,24,31,32,33,34,35,],[18,22,22,22,22,22,22,22,22,]),'LBRACKET':([9,15,],[19,19,]),'COMMA':([10,11,13,15,37,],[-10,-11,20,-9,-12,]),'FLOAT':([18,22,24,31,32,33,34,35,],[26,26,26,26,26,26,26,26,]),'MIN':([18,21,22,23,24,25,26,29,31,32,33,34,35,36,38,39,40,41,42,43,],[24,-25,24,32,24,-22,-23,32,24,24,24,24,24,-24,-21,-16,-17,-18,-19,-20,]),'RPAREN':([21,23,25,26,29,36,38,39,40,41,42,43,],[-25,30,-22,-23,38,-24,-21,-16,-17,-18,-19,-20,]),'PLUS':([21,23,25,26,29,36,38,39,40,41,42,43,],[-25,31,-22,-23,31,-24,-21,-16,-17,-18,-19,-20,]),'DIV':([21,23,25,26,29,36,38,39,40,41,42,43,],[-25,33,-22,-23,33,-24,-21,33,33,-18,-19,-20,]),'POW':([21,23,25,26,29,36,38,39,40,41,42,43,],[-25,34,-22,-23,34,-24,-21,34,34,34,-19,34,]),'MUL':([21,23,25,26,29,36,38,39,40,41,42,43,],[-25,35,-22,-23,35,-24,-21,35,35,-18,-19,-20,]),'RBRACKET':([27,],[37,]),} 12 | 13 | _lr_action = {} 14 | for _k, _v in _lr_action_items.items(): 15 | for _x,_y in zip(_v[0],_v[1]): 16 | if not _x in _lr_action: _lr_action[_x] = {} 17 | _lr_action[_x][_k] = _y 18 | del _lr_action_items 19 | 20 | _lr_goto_items = {'program':([0,],[1,]),'instruction':([0,1,],[2,12,]),'normal_instruction':([0,1,8,],[3,3,17,]),'adjoint_instruction':([0,1,],[4,4,]),'term':([0,1,6,7,8,20,],[6,6,13,13,6,13,]),'param_term':([0,1,8,],[7,7,7,]),'lookup':([0,1,6,7,8,20,],[11,11,11,11,11,11,]),'argument_list':([6,7,20,],[14,16,28,]),'expression':([18,22,24,31,32,33,34,35,],[23,29,36,39,40,41,42,43,]),} 21 | 22 | _lr_goto = {} 23 | for _k, _v in _lr_goto_items.items(): 24 | for _x, _y in zip(_v[0], _v[1]): 25 | if not _x in _lr_goto: _lr_goto[_x] = {} 26 | _lr_goto[_x][_k] = _y 27 | del _lr_goto_items 28 | _lr_productions = [ 29 | ("S' -> program","S'",1,None,None,None), 30 | ('program -> program instruction','program',2,'p_program','parser.py',26), 31 | ('program -> instruction','program',1,'p_program','parser.py',27), 32 | ('instruction -> normal_instruction','instruction',1,'p_instruction','parser.py',40), 33 | ('instruction -> adjoint_instruction','instruction',1,'p_instruction','parser.py',41), 34 | ('normal_instruction -> term argument_list','normal_instruction',2,'p_normal_instruction','parser.py',45), 35 | ('normal_instruction -> param_term argument_list','normal_instruction',2,'p_normal_instruction','parser.py',46), 36 | ('adjoint_instruction -> ADJ normal_instruction','adjoint_instruction',2,'p_adjoint_instruction','parser.py',50), 37 | ('instruction -> NEWLINE','instruction',1,'p_instruction_newline','parser.py',61), 38 | ('term -> IDENT','term',1,'p_term','parser.py',65), 39 | ('term -> INTEGER','term',1,'p_term','parser.py',66), 40 | ('term -> lookup','term',1,'p_term','parser.py',67), 41 | ('lookup -> IDENT LBRACKET INTEGER RBRACKET','lookup',4,'p_lookup','parser.py',71), 42 | ('param_term -> IDENT LPAREN expression RPAREN','param_term',4,'p_param_term','parser.py',75), 43 | ('argument_list -> term','argument_list',1,'p_argument_list','parser.py',79), 44 | ('argument_list -> term COMMA argument_list','argument_list',3,'p_argument_list_args','parser.py',83), 45 | ('expression -> expression PLUS expression','expression',3,'p_expression_bin_op','parser.py',88), 46 | ('expression -> expression MIN expression','expression',3,'p_expression_bin_op','parser.py',89), 47 | ('expression -> expression DIV expression','expression',3,'p_expression_bin_op','parser.py',90), 48 | ('expression -> expression POW expression','expression',3,'p_expression_bin_op','parser.py',91), 49 | ('expression -> expression MUL expression','expression',3,'p_expression_bin_op','parser.py',92), 50 | ('expression -> LPAREN expression RPAREN','expression',3,'p_expression_group','parser.py',107), 51 | ('expression -> INTEGER','expression',1,'p_expression_integer','parser.py',111), 52 | ('expression -> FLOAT','expression',1,'p_expression_float','parser.py',115), 53 | ('expression -> MIN expression','expression',2,'p_expression_unary_min','parser.py',119), 54 | ('expression -> IDENT','expression',1,'p_expression_ident','parser.py',123), 55 | ] 56 | -------------------------------------------------------------------------------- /qsy/backends/chp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from qsy import gates 4 | from qsyasm.log import print_info 5 | 6 | from .backend import Backend 7 | 8 | 9 | class CHPBackend(Backend): 10 | ''' 11 | CHPBackend can efficiently simulate stabilizer circuits (circuits consisting 12 | of only CNOT, H and phase gates). The algorithm used to achieve this is 13 | described in a paper by Scott Aaronson and Daniel Gottesman found at 14 | https://arxiv.org/abs/quant-ph/0406196. 15 | ''' 16 | 17 | SUPPORTED_GATES = [gates.CX, gates.H, gates.S, gates.X, gates.Y, gates.Z, 18 | gates.CZ] 19 | 20 | def __init__(self, size, name): 21 | self.name = name 22 | self.size = size 23 | 24 | # Initialize tableau 25 | # X generators 26 | self.x = np.concatenate( 27 | (np.eye(self.size, dtype=np.int8), 28 | np.zeros((self.size, self.size), dtype=np.int8), 29 | # scratch space 30 | np.zeros((1, self.size), dtype=np.int8)), 31 | axis=0 32 | ) 33 | 34 | # Z generators 35 | self.z = np.concatenate( 36 | (np.zeros((self.size, self.size), dtype=np.int8), 37 | np.eye(self.size, dtype=np.int8), 38 | # scratch space 39 | np.zeros((1, self.size), dtype=np.int8)), 40 | axis=0 41 | ) 42 | 43 | # Phase (0 for +1, 1 for i, 2 for -1, 3 for -i) 44 | self.r = np.zeros((2*self.size + 1, 1), dtype=np.int8) 45 | 46 | def apply_gate(self, gate, *params, adjoint=False): 47 | if gate not in self.SUPPORTED_GATES: 48 | supported_gate_names = map(lambda g: g.name, self.SUPPORTED_GATES) 49 | raise Exception( 50 | 'Unsupported gate "{}" for CHP back-end. '.format(gate.name) + 51 | 'Supported gates are {}.'.format(', '.join(supported_gate_names)) 52 | ) 53 | 54 | if gate.arity == 1: 55 | target = params[0] 56 | else: 57 | control, target = params 58 | 59 | self._check_in_range(control) 60 | 61 | self._check_in_range(target) 62 | 63 | if gate == gates.CX: 64 | self._cnot(control, target) 65 | elif gate == gates.H: 66 | self._h(target) 67 | elif gate == gates.S: 68 | if adjoint: 69 | # S^dagger = SSS 70 | self._s(target) 71 | self._s(target) 72 | 73 | self._s(target) 74 | elif gate == gates.X: 75 | self._x(target) 76 | elif gate == gates.Y: 77 | self._y(target) 78 | elif gate == gates.Z: 79 | self._z(target) 80 | elif gate == gates.CZ: 81 | self._cz(control, target) 82 | 83 | def measure(self, target): 84 | p = None 85 | for i in range(self.size, 2*self.size): 86 | if self.x[i, target] == 1: 87 | p = i 88 | break 89 | 90 | if p is not None: 91 | # Measurement outcome is probabilistic 92 | for i in range(2*self.size): 93 | if i != p and self.x[i, target] == 1: 94 | self._rowsum(i, p) 95 | 96 | # Set (p−n)th row equal to pth row 97 | self.x[p-self.size] = self.x[p] 98 | self.z[p-self.size] = self.z[p] 99 | self.r[p-self.size] = self.r[p] 100 | 101 | self.x[p] = 0 102 | self.z[p] = 0 103 | 104 | measurement = np.random.randint(2) 105 | self.r[p] = measurement 106 | 107 | self.z[p, target] = 1 108 | 109 | return measurement 110 | else: 111 | # Measurement outcome is deterministic 112 | self.x[2*self.size] = 0 113 | self.z[2*self.size] = 0 114 | self.r[2*self.size] = 0 115 | 116 | for i in range(self.size): 117 | if self.x[i, target] == 1: 118 | self._rowsum(2*self.size, i + self.size) 119 | 120 | return int(self.r[2*self.size]) 121 | 122 | def measure_all(self): 123 | return [self.measure(i) for i in range(self.size)] 124 | 125 | def yield_state(self): 126 | print_info( 127 | 'Printing quantum state is not supported by the CHP back-end. ' + 128 | 'Use the statevector back-end if you wish to see the quantum state.' 129 | ) 130 | yield from [] 131 | 132 | def to_dirac(self): 133 | # TODO: make CHP state printable/readable 134 | return '' 135 | 136 | def _h(self, target): 137 | for i in range(2*self.size): 138 | self.r[i] ^= self.x[i, target] & self.z[i, target] 139 | self.x[i, target], self.z[i, target] = self.z[i, target], self.x[i, target] 140 | 141 | def _cnot(self, control, target): 142 | for i in range(2*self.size): 143 | self.r[i] ^= self.x[i, control] & self.z[i, target] 144 | self.r[i] &= self.x[i, target] ^ self.z[i, control] ^ 1 145 | 146 | self.x[i, target] ^= self.x[i, control] 147 | self.z[i, control] ^= self.z[i, target] 148 | 149 | def _s(self, target): 150 | for i in range(2*self.size): 151 | self.r[i] ^= self.x[i, target] & self.z[i, target] 152 | self.z[i, target] ^= self.x[i, target] 153 | 154 | def _x(self, target): 155 | self._h(target) 156 | self._z(target) 157 | self._h(target) 158 | 159 | def _y(self, target): 160 | self._z(target) 161 | self._h(target) 162 | self._z(target) 163 | self._h(target) 164 | 165 | def _z(self, target): 166 | self._s(target) 167 | self._s(target) 168 | 169 | def _cz(self, control, target): 170 | self._h(target) 171 | self._cnot(control, target) 172 | self._h(target) 173 | 174 | def _rowsum(self, h, i): 175 | ''' 176 | Set generator h equal to h + i. Keep track of the factors of i that r_h 177 | goes through when multiplying Pauli matrices. 178 | ''' 179 | def g(x1, z1, x2, z2): 180 | ''' 181 | Return the exponent to which i is raised (either 0, 1, or −1) when 182 | the Pauli matrices represented by x1 z1 and x2 z2 are multiplied. 183 | ''' 184 | if x1 == z1 == 0: 185 | return 0 186 | if x1 == z1 == 1: 187 | return z2 - x2 188 | if x1 == 1 and z1 == 0: 189 | return z2 * (2*x2 - 1) 190 | if x1 == 0 and z1 == 1: 191 | return x2 * (1 - 2*z2) 192 | 193 | rowsum = sum(g(self.x[i, j], self.z[i, j], self.x[h, j], self.z[h, j]) 194 | for j in range(self.size)) 195 | rowsum += 2*self.r[h] + 2*self.r[i] 196 | 197 | if rowsum == 0: 198 | self.r[h] = 0 199 | elif rowsum == 2 % 4: 200 | self.r[h] = 1 201 | 202 | for j in range(self.size): 203 | self.x[h, j] ^= self.x[i, j] 204 | self.z[h, j] ^= self.z[i, j] 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qsy 2 | A quantum computer state vector/stabilizer circuit simulator and assembly 3 | language. 4 | 5 | ## Table of Contents 6 | * [Installation](#installation) 7 | * [qsy](#qsy-1) 8 | * [Example](#example) 9 | * [qsyASM](#qsyasm) 10 | * [Usage](#usage) 11 | * [Example](#example-1) 12 | * [Syntax](#syntax) 13 | * [Operations](#operations) 14 | * [Adjoint Operation](#adjoint-operation) 15 | * [List of Operations](#list-of-operations) 16 | * [Registers](#registers) 17 | * [Measurement](#measurement) 18 | * [Efficient simulation of stabilizer circuits](#efficient-simulation-of-stabilizer-circuits) 19 | * [License](#license) 20 | 21 | ## Installation 22 | ``` 23 | $ pip install qsy 24 | ``` 25 | 26 | This will install the Python library qsy and command-line tool qsyasm. 27 | 28 | ## qsy 29 | qsy is a Python library for simulating quantum circuits. 30 | 31 | ### Example 32 | The following code creates an entangled state and prints its state vector in 33 | Dirac notation. 34 | ```python 35 | from qsy import QuantumRegister, gates 36 | 37 | qr = QuantumRegister(2) 38 | qr.apply_gate(gates.H, 0) 39 | qr.apply_gate(gates.CX, 0, 1) 40 | 41 | print(qr.to_dirac()) 42 | ``` 43 | The output will be: 44 | ``` 45 | +0.70711|00> +0.70711|11> 46 | ``` 47 | 48 | ## qsyASM 49 | qsyASM is a quantum assembly language acting as front-end for qsy. It allows 50 | you to quickly write and debug quantum programs. It also allows for efficient 51 | simulation of stabilizer circuits using the `chp` back-end. 52 | 53 | ### Usage 54 | ``` 55 | usage: qsyasm [-h] [-V] [-v] [-t] [-b B] [-s SHOTS] [--ignore-print-warning] 56 | [--skip-zero-amplitudes] 57 | filename 58 | 59 | qsyasm assembly runner 60 | 61 | positional arguments: 62 | filename qsyasm file to execute 63 | 64 | optional arguments: 65 | -h, --help show this help message and exit 66 | -V, --version show program's version number and exit 67 | -v, --verbose verbose output 68 | -t, --time time program execution 69 | -b B, --backend B simulator back-end to use: chp or statevector 70 | (default: statevector) 71 | -s SHOTS, --shots SHOTS 72 | amount of shots to run 73 | --ignore-print-warning 74 | ignore register too large to print warning 75 | --skip-zero-amplitudes 76 | don't print states with an amplitude of 0 77 | ``` 78 | 79 | ### Example 80 | The following qsyASM program creates an entangled state and measures to a 81 | classical register: 82 | ```asm 83 | qreg[2] q 84 | creg[2] c 85 | 86 | h q[0] 87 | cx q[0], q[1] 88 | 89 | meas q, c 90 | ``` 91 | Running it: 92 | ``` 93 | $ qsyasm examples/qsyasm/bell.qs 94 | q[2]: +1|11> 95 | +0 | 00 96 | +0 | 01 97 | +0 | 10 98 | +1 | 11 99 | c[2]: 11 100 | ``` 101 | Or running it a number of times: 102 | ``` 103 | $ qsyasm examples/qsyasm/bell.qs --shots=1024 104 | q[2]: +1|00> 105 | +1 | 00 106 | +0 | 01 107 | +0 | 10 108 | +0 | 11 109 | c[2]: {'11': 550, '00': 474} 110 | ``` 111 | More examples such as the quantum phase estimation algorithm can be found in the 112 | [examples/qsyasm](examples/qsyasm) folder. 113 | 114 | ### Syntax 115 | The structure of a qsyASM program consists of a list of instructions. An 116 | instruction is defined as an operation followed by its arguments. 117 | 118 | #### Operations 119 | The instruction 120 | ```asm 121 | cx q[0], q[1] 122 | ``` 123 | applies a CNOT operation with control qubit `q[0]` and target qubit `q[1]`. 124 | Some operations take an angle (in radians) as argument. The parameterized operation 125 | ```asm 126 | rz(pi/2) q[0] 127 | ``` 128 | rotates `q[0]` π/2 radians around the Z axis. Expressions are allowed in 129 | parameterized operations. Expression operators supported are `+`, `-`, `*`, `/` 130 | and `**` (power). The variable `pi` is available for convenience. 131 | 132 | ##### Adjoint Operation 133 | To apply the adjoint of a gate, the `adj` keyword is available. For example, to 134 | apply the adjoint of S (S dagger): 135 | ```asm 136 | adj s q[0] 137 | ``` 138 | 139 | ##### List of Operations 140 | | Gate | qsyASM operation | 141 | |----------|----------------------------------| 142 | | Pauli I | `i target` | 143 | | Pauli X | `x target` | 144 | | Pauli Y | `y target` | 145 | | Pauli Z | `z target` | 146 | | Hadamard | `h target` | 147 | | S | `s target` | 148 | | T | `t target` | 149 | | Rx | `rx(angle) target` | 150 | | Ry | `ry(angle) target` | 151 | | Rz | `rz(angle) target` | 152 | | CNOT | `cx control, target` | 153 | | CZ | `cz control, target` | 154 | | CRx | `crx(angle) control, target` | 155 | | CRy | `cry(angle) control, target` | 156 | | CRz | `crz(angle) control, target` | 157 | | Toffoli | `ccx controlA, controlB, target` | 158 | 159 | #### Registers 160 | Defining a quantum register is done with the `qreg` operation. The instruction 161 | ```asm 162 | qreg[5] q 163 | ``` 164 | defines a 5 qubit quantum register named `q`. Likewise, a classical register (useful for measuring) can be defined as 165 | ```asm 166 | creg[5] c 167 | ``` 168 | Qubits in a quantum register are initiated to |0⟩, and bits in a classical register to 0. 169 | 170 | #### Measurement 171 | Measurement can be done on individual qubits, or a complete quantum state. The program 172 | ```asm 173 | qreg[5] q 174 | creg[1] c 175 | 176 | h q[0] 177 | 178 | meas q[0], c[0] 179 | ``` 180 | measures `q[0]` to `c[0]`, collapsing the state and storing the result in `c[0]`. The measurement result can be ignored by only passing one argument to `meas`: 181 | ```asm 182 | meas q[0] 183 | ``` 184 | 185 | To measure a complete quantum state you can pass the whole quantum and classical register: 186 | ```asm 187 | qreg[3] q 188 | creg[3] c 189 | 190 | ; 3 qubit GHZ state 191 | h q[0] 192 | cx q[0], q[1] 193 | cx q[0], q[2] 194 | 195 | meas q, c 196 | ``` 197 | collapsing the quantum register `q` and storing the measurement result in `c`. This only works when the quantum register and classical register are equal in size. 198 | 199 | ### Efficient simulation of stabilizer circuits 200 | Circuits consisting only of CNOT, H, S, X, Y, Z and CZ gates can be efficiently 201 | simulated with the CHP back-end. Using any other operations with the CHP 202 | back-end will result in an error. 203 | 204 | For example, we can simulate a partially entangled 750 qubit state: 205 | ``` 206 | $ qsyasm examples/qsyasm/750_qubits.qs --backend=chp 207 | c[750]: 000000000000000000000000000000000000000000000000001111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 208 | ``` 209 | 210 | This back-end is an implementation of the CHP simulator described by 211 | Scott Aaronson and Daniel Gottesman in their paper "Improved Simulation of Stabilizer Circuits" 212 | ([arXiv:quant-ph/0406196](https://arxiv.org/abs/quant-ph/0406196)). 213 | 214 | ## License 215 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file 216 | for the full license. 217 | -------------------------------------------------------------------------------- /qsyasm/program.py: -------------------------------------------------------------------------------- 1 | import time 2 | from collections import defaultdict 3 | 4 | import numpy as np 5 | 6 | from qsy import __version__, gates 7 | from qsy.backends import CHPBackend, StatevectorBackend 8 | from qsy.error import InvalidRegisterError, RegisterIndexError 9 | from qsy.util import format_complex 10 | 11 | from .env import Env 12 | from .error import ParseError, QsyASMError 13 | from .instruction import Operation 14 | from .interpreter.parser import QsyASMParser 15 | from .log import print_info, print_warning 16 | 17 | OPERATION_GATES = { 18 | Operation.I: gates.I, 19 | Operation.X: gates.X, 20 | Operation.Y: gates.Y, 21 | Operation.Z: gates.Z, 22 | 23 | Operation.H: gates.H, 24 | Operation.S: gates.S, 25 | Operation.CX: gates.CX, 26 | Operation.CY: gates.CY, 27 | Operation.CZ: gates.CZ, 28 | 29 | Operation.T: gates.T, 30 | Operation.RX: gates.Rx, 31 | Operation.RY: gates.Ry, 32 | Operation.RZ: gates.Rz, 33 | Operation.CRX: gates.CRx, 34 | Operation.CRY: gates.CRy, 35 | Operation.CRZ: gates.CRz, 36 | Operation.CCX: gates.CCX 37 | } 38 | 39 | 40 | class QsyASMProgram: 41 | MAX_PRINTABLE_QUBITS = 16 42 | 43 | def __init__(self, args): 44 | self.filename = args['filename'] 45 | 46 | try: 47 | with open(self.filename) as f: 48 | self.input = f.read() 49 | except Exception as e: 50 | raise QsyASMError('Error reading input: {}'.format(str(e))) 51 | 52 | self.time = args['time'] 53 | self.verbose = args['verbose'] 54 | self.ignore_print_warning = args['ignore_print_warning'] 55 | self.skip_zero_amplitudes = args['skip_zero_amplitudes'] 56 | 57 | self.shots = args['shots'] 58 | self.measurement_results = {} 59 | 60 | self.backend_arg = args['backend'] 61 | if self.backend_arg == 'chp': 62 | self.backend = CHPBackend 63 | else: 64 | self.backend = StatevectorBackend 65 | 66 | self._verbose_print('Using {} backend'.format(self.backend.__name__)) 67 | 68 | self.parser = QsyASMParser() 69 | self.env = Env() 70 | 71 | def run(self): 72 | print_info('qsyasm v{}'.format(__version__)) 73 | print_info('A state vector/stabilizer circuit simulator assembly runner') 74 | print_info('=' * 60) 75 | 76 | start = time.time() 77 | 78 | try: 79 | instructions = self.parser.parse(self.input) 80 | except ParseError as e: 81 | raise QsyASMError(self._error_message(e.msg, e.lexpos, e.lineno)) 82 | 83 | # TODO: we can auto-detect stabilizer circuits and use the CHP back-end 84 | # when it is capable of printing its state so no features are lost. 85 | # if self._can_use_chp(instructions) and self.backend_arg is None: 86 | # print_info('Stabilizer circuit detected, using CHP back-end') 87 | # self.backend = CHPBackend 88 | 89 | self._verbose_print('Executing {} shots'.format(self.shots)) 90 | 91 | for _ in range(self.shots): 92 | self.eval(instructions) 93 | self._save_measurements() 94 | 95 | end = time.time() 96 | 97 | self.dump_registers() 98 | 99 | if self.time: 100 | print_info('Program execution took {:.5f} seconds'.format(end - start)) 101 | 102 | def eval(self, instructions): 103 | for instr in instructions: 104 | try: 105 | if instr.is_gate(): 106 | self._eval_gate(instr) 107 | elif instr.type == Operation.QR or instr.type == Operation.CR: 108 | self._eval_register(instr) 109 | elif instr.type == Operation.MEASURE: 110 | self._eval_measure(instr) 111 | elif instr.type == Operation.ERROR: 112 | raise QsyASMError( 113 | self._error_message( 114 | 'Undefined operation "{}"'.format(instr.op[0]), 115 | instr.lexpos, 116 | instr.lineno 117 | ) 118 | ) 119 | except (RegisterIndexError, InvalidRegisterError) as e: 120 | raise QsyASMError(self._error_message(e, instr.lexpos, instr.lineno)) 121 | 122 | def dump_registers(self): 123 | for qr_name, qr in self.env.qrs.items(): 124 | if qr.size > self.MAX_PRINTABLE_QUBITS and not self.ignore_print_warning: 125 | print_warning( 126 | 'Quantum register {} too large to print ({} qubits)'.format( 127 | qr.name, qr.size) + 128 | ' (to print the state anyway, pass ' + 129 | '--ignore-print-warning as argument)') 130 | continue 131 | 132 | print('{}[{}]: {}'.format(qr_name, qr.size, qr.to_dirac())) 133 | 134 | for i, amplitude in qr.yield_state(): 135 | if np.isclose(amplitude, 0) and self.skip_zero_amplitudes: 136 | continue 137 | 138 | amplitude = format_complex(amplitude) 139 | print('{:>8} |{:0{size}b}>'.format(amplitude, i, size=qr.size)) 140 | 141 | for cr_name, cr in self.env.crs.items(): 142 | if self.shots > 1 and self.measurement_results: 143 | bits = dict(self.measurement_results[cr_name]) 144 | else: 145 | bits = ''.join(str(bit) for bit in cr.state) 146 | 147 | print('{}[{}]: {}'.format(cr_name, cr.size, bits)) 148 | 149 | def _eval_gate(self, instr): 150 | gate = OPERATION_GATES[instr.type] 151 | args = instr.args 152 | 153 | # TODO: multi qubit gates across registers 154 | register = args[0][0] 155 | targets = [arg[1] for arg in args] 156 | 157 | if callable(gate): 158 | gate_arg = instr.op[1] 159 | gate = gate(gate_arg) 160 | 161 | adjoint_message = 'adjoint ' if instr.adjoint else '' 162 | self._verbose_print('Applying gate {}{} on {}{}'.format( 163 | adjoint_message, gate.name, register, targets)) 164 | 165 | self.env.qr(register).apply_gate(gate, *targets, adjoint=instr.adjoint) 166 | 167 | def _eval_register(self, instr): 168 | if len(instr.op) != 2: 169 | raise QsyASMError( 170 | self._error_message( 171 | 'Missing register size in {} definition'.format(instr.op_name), 172 | instr.lexpos, instr.lineno 173 | ) 174 | ) 175 | 176 | register_size = instr.op[1] 177 | 178 | if instr.type == Operation.QR: 179 | for register_name in instr.args: 180 | self.env.create_qr(register_name, register_size, self.backend) 181 | elif instr.type == Operation.CR: 182 | for register_name in instr.args: 183 | self.env.create_cr(register_name, register_size) 184 | 185 | def _eval_measure(self, instr): 186 | qtarget = instr.args[0] 187 | qtarget_name = qtarget[0] 188 | 189 | if len(instr.args) == 2 and len(instr.args[0]) != len(instr.args[1]): 190 | raise QsyASMError( 191 | self._error_message( 192 | 'Mismatched register sizes in measurement', 193 | instr.lexpos, 194 | instr.lineno 195 | ) 196 | ) 197 | 198 | if len(qtarget) == 1: 199 | # Measure all 200 | measured = self.env.qr(qtarget_name).measure_all() 201 | elif len(qtarget) == 2: 202 | # Measure single qubit 203 | qubit = qtarget[1] 204 | measured = self.env.qr(qtarget_name).measure(qubit) 205 | 206 | if len(instr.args) == 2: 207 | # Save measurement to classical register 208 | ctarget = instr.args[1] 209 | 210 | if len(ctarget) == 1: 211 | if len(qtarget) == 1: 212 | # Ensure the classical and quantum registers are of the same size 213 | # when measuring all qubits 214 | qtarget_size = self.env.qr(qtarget).size 215 | ctarget_size = self.env.cr(ctarget).size 216 | 217 | if qtarget_size != ctarget_size: 218 | raise QsyASMError( 219 | self._error_message( 220 | 'Mismatched register sizes in measurement ({}[{}] and {}[{}])'.format( 221 | qtarget_name, qtarget_size, ctarget[0], ctarget_size 222 | ), 223 | instr.lexpos, 224 | instr.lineno 225 | ) 226 | ) 227 | 228 | self.env.cr(ctarget).set_state(measured) 229 | elif len(ctarget) == 2: 230 | ctarget, register_index = ctarget 231 | self.env.cr(ctarget)[register_index] = measured 232 | 233 | # Save measurement results when shots > 1 234 | if self.shots > 1 and ctarget not in self.measurement_results: 235 | self.measurement_results[ctarget] = defaultdict(int) 236 | 237 | def _save_measurements(self): 238 | for cr_name in self.measurement_results.keys(): 239 | cr_value = self.env.cr(cr_name).state 240 | bit_string = ''.join(str(bit) for bit in cr_value) 241 | self.measurement_results[cr_name][bit_string] += 1 242 | 243 | def _can_use_chp(self, instructions): 244 | ''' 245 | Determine if a program is a stabilizer circuit and can use the CHP 246 | back-end. 247 | ''' 248 | for instr in instructions: 249 | if instr.is_gate(): 250 | gate = OPERATION_GATES[instr.type] 251 | if gate not in CHPBackend.SUPPORTED_GATES: 252 | return False 253 | 254 | return True 255 | 256 | def _verbose_print(self, msg): 257 | if self.verbose: 258 | print_info(msg) 259 | 260 | def _error_message(self, msg, lexpos, lineno): 261 | column = self._find_column(lexpos) 262 | return '{}:{}:{}: {}'.format(self.filename, lineno, column, msg) 263 | 264 | def _find_column(self, lexpos): 265 | line_start = self.input.rfind('\n', 0, lexpos) + 1 266 | return (lexpos - line_start) + 1 267 | --------------------------------------------------------------------------------