├── .gitignore ├── .travis.yml ├── README.md ├── examples ├── __init__.py ├── plus_equal_product.py ├── plus_equal_product_mod.py ├── plus_equal_product_mod_test.py ├── plus_equal_product_test.py ├── times_equal.py ├── times_equal_exp_mod.py ├── times_equal_exp_mod_test.py └── times_equal_test.py ├── quantumpseudocode ├── __init__.py ├── arithmetic │ ├── __init__.py │ ├── add.py │ ├── add_test.py │ ├── cmp.py │ ├── cmp_test.py │ ├── coherent_mul.py │ ├── coherent_mul_test.py │ ├── lookup.py │ ├── lookup_test.py │ ├── measure.py │ ├── mul.py │ ├── mul_test.py │ ├── mult_add.py │ ├── mult_add_test.py │ ├── phase_flip.py │ ├── swap.py │ ├── unary.py │ ├── unary_test.py │ ├── xor.py │ └── xor_test.py ├── arithmetic_mod │ ├── __init__.py │ ├── add_mod.py │ └── add_mod_test.py ├── buf │ ├── __init__.py │ ├── int_buffer.py │ └── int_buffer_test.py ├── control.py ├── control_test.py ├── log_cirq.py ├── lvalue │ ├── __init__.py │ ├── padded.py │ ├── qubit.py │ ├── qubit_test.py │ ├── quint.py │ ├── quint_mod.py │ ├── quint_mod_test.py │ ├── quint_test.py │ ├── qureg.py │ └── qureg_test.py ├── ops │ ├── __init__.py │ ├── operation.py │ ├── semi_quantum.py │ └── semi_quantum_test.py ├── qalloc.py ├── requirements.txt ├── rvalue │ ├── __init__.py │ ├── backed.py │ ├── const.py │ ├── expression.py │ ├── expression_test.py │ ├── hold.py │ ├── lookup.py │ └── rvalue.py ├── shor │ ├── __init__.py │ ├── measure_pow_mod.py │ └── measure_pow_mod_test.py ├── sim.py ├── sim_test.py ├── sink.py ├── testing │ ├── __init__.py │ ├── sim_call.py │ ├── verify_semi_quantum.py │ └── verify_semi_quantum_test.py ├── util.py └── util_test.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | **/.vs/* 2 | **/obj/* 3 | **/bin/* 4 | **/.idea/* 5 | **/__pycache__/* 6 | **/.venv/* 7 | **/.cache/* 8 | 9 | main.aux 10 | main.bbl 11 | main.blg 12 | main.log 13 | main.out 14 | main.pdf 15 | mainNotes.bib 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: "3.6" 3 | install: pip install -r requirements.txt 4 | script: pytest 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Quantum Pseudo Code 2 | ------------------- 3 | 4 | When attempting to do arithmetic with quantum computing languages, one is struck 5 | by the lack of basic built-in constructs such as addition. 6 | For example, it is rare to be able to type `a += b` instead of 7 | `CuccaroLittleEndianAdder(b, a)`. 8 | This library's goal is to be like the former, instead of the latter. 9 | 10 | For example, assuming `a`, `b`, and `c` are quantum values, the line 11 | 12 | ```python 13 | a += table[b] & controlled_by(c) 14 | ``` 15 | 16 | will perform the following steps: 17 | 18 | 1. Allocate a temporary quantum register. 19 | 2. Initialize the temporary register using a controlled QROM lookup with address `b` and control `c`. 20 | 3. Add the temporary register's value into `a`. 21 | 4. Uncompute the controlled QROM lookup. 22 | 5. Release the temporary register. 23 | 24 | This works because the right hand expression `table[b] & controlled_by(c)` 25 | produces a `quantumpseudocode.RValue[int]`, specifically a `ControlledRValue` containing a 26 | `LookupRValue`. 27 | Every RValue class knows how to initialize registers so that they store the 28 | value of its expression, and also how to clear those registers. 29 | So, when the `quantumpseudocode.Quint.__iadd__` method is invoked with an rvalue, it can 30 | ask that rvalue to please initialize a temporary register so it can be added 31 | into the target of the addition. 32 | 33 | Note that quantumpseudocode contains the bare minimum to make implementing Shor's algorithm 34 | work. 35 | There are a lot of holes in the functionality. 36 | Also it's not very fast. 37 | But it does make it possible to include in a paper what at first glance looks 38 | like pseudo-code, but have that code actually be executable. 39 | 40 | For example: 41 | 42 | ```python 43 | from quantumpseudocode import * 44 | 45 | def make_coset_register(value: int, length: int, modulus: int) -> Quint: 46 | reg: Quint = qalloc(len=length) 47 | reg ^= value % modulus 48 | 49 | # Add coherent multiple of modulus into reg. 50 | pad_bits = length - modulus.bit_length() 51 | for i in range(pad_bits): 52 | offset = modulus << i 53 | q = qalloc(x_basis=True) 54 | reg += offset & controlled_by(q) 55 | qfree(q, equivalent_expression=reg >= offset) 56 | 57 | return reg 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strilanc/quantumpseudocode/da4e86e8a8709bab6ca5f199f0aa8799a6b1248d/examples/__init__.py -------------------------------------------------------------------------------- /examples/plus_equal_product.py: -------------------------------------------------------------------------------- 1 | from quantumpseudocode import * 2 | 3 | 4 | def plus_equal_product_builtin(target: Quint, k: int, y: Quint): 5 | target += k * y 6 | 7 | 8 | def plus_equal_product_iter_quantum(target: Quint, k: int, y: Quint): 9 | for i in range(len(y)): 10 | target[i:] += k & controlled_by(y[i]) 11 | 12 | 13 | def plus_equal_product_iter_classical(target: Quint, k: int, y: Quint): 14 | for i in range(k.bit_length()): 15 | if (k >> i) & 1: 16 | target[i:] += y 17 | 18 | 19 | def plus_equal_product_single_lookup(target: Quint, k: int, y: Quint): 20 | table = LookupTable([0, k]) 21 | for i in range(len(y)): 22 | target[i:] += table[y[i]] 23 | 24 | 25 | def plus_equal_product_windowed(target: Quint, 26 | k: int, 27 | y: Quint, 28 | window: int): 29 | table = LookupTable([ 30 | i*k 31 | for i in range(2**window) 32 | ]) 33 | for i in range(0, len(y), window): 34 | target[i:] += table[y[i:i+window]] 35 | 36 | 37 | def plus_equal_product_windowed_2(target: Quint, 38 | k: int, 39 | y: Quint): 40 | return plus_equal_product_windowed(target, k, y, 2) 41 | 42 | 43 | def plus_equal_product_windowed_4(target: Quint, 44 | k: int, 45 | y: Quint): 46 | return plus_equal_product_windowed(target, k, y, 4) 47 | 48 | 49 | def plus_equal_product_windowed_lg(target: Quint, 50 | k: int, 51 | y: Quint): 52 | return plus_equal_product_windowed(target, k, y, max(1, ceil_lg2(len(y)))) 53 | -------------------------------------------------------------------------------- /examples/plus_equal_product_mod.py: -------------------------------------------------------------------------------- 1 | from quantumpseudocode import * 2 | 3 | 4 | def plus_equal_product_mod_classic(target: QuintMod, 5 | k: int, 6 | y: Quint): 7 | N = target.modulus 8 | for i in range(len(y)): 9 | target += (k * 2**i % N) & controlled_by(y[i]) 10 | 11 | 12 | def plus_equal_product_mod_windowed(target: QuintMod, 13 | k: int, 14 | y: Quint, 15 | window: int): 16 | N = target.modulus 17 | for i in range(0, len(y), window): 18 | w = y[i:i + window] 19 | table = LookupTable([ 20 | j * k * 2**i % N 21 | for j in range(2**window) 22 | ]) 23 | target += table[w] 24 | 25 | 26 | def plus_equal_product_mod_windowed_2(target: QuintMod, 27 | k: int, 28 | y: Quint): 29 | plus_equal_product_mod_windowed(target, k, y, 2) 30 | 31 | 32 | def plus_equal_product_mod_windowed_4(target: QuintMod, 33 | k: int, 34 | y: Quint): 35 | plus_equal_product_mod_windowed(target, k, y, 4) 36 | 37 | 38 | def plus_equal_product_mod_windowed_lg(target: QuintMod, 39 | k: int, 40 | y: Quint): 41 | w = max(1, ceil_lg2(len(target))) 42 | plus_equal_product_mod_windowed(target, k, y, w) 43 | -------------------------------------------------------------------------------- /examples/plus_equal_product_mod_test.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import random 3 | 4 | import pytest 5 | 6 | import quantumpseudocode as qp 7 | from .plus_equal_product_mod import * 8 | 9 | 10 | methods = [ 11 | plus_equal_product_mod_classic, 12 | plus_equal_product_mod_windowed_2, 13 | plus_equal_product_mod_windowed_4, 14 | plus_equal_product_mod_windowed_lg, 15 | ] 16 | 17 | cases = [ 18 | {'N': 10, 't': 5, 'k': 7, 'y': 13}, 19 | {'N': 4, 't': 5, 'k': 7, 'y': 13}, 20 | {'N': 1, 't': 1, 'k': 1, 'y': 1}, 21 | {'k': 847561784, 'N': 30, 't': 771685281, 'y': 541683290}, 22 | {'k': 2, 'N': 2, 't': 0, 'y': 2}, 23 | {'k': 2, 'N': 2, 't': 1, 'y': 2}, 24 | { 25 | 'N': 30, 26 | 't': random.randint(0, 2**30-1), 27 | 'k': random.randint(0, 2**30-1), 28 | 'y': random.randint(0, 2**30-1) 29 | }, 30 | ] 31 | 32 | 33 | @pytest.mark.parametrize( 34 | 'method,case', 35 | itertools.product(methods, cases) 36 | ) 37 | def test_correct_result(method, case): 38 | modulus = case['N'] 39 | t = case['t'] 40 | k = case['k'] 41 | y = case['y'] 42 | t %= modulus 43 | y %= modulus 44 | k %= modulus 45 | 46 | final_state = qp.testing.sim_call( 47 | method, 48 | target=qp.testing.ModInt(val=t, modulus=modulus), 49 | k=k, 50 | y=y) 51 | assert final_state == qp.ArgsAndKwargs([], { 52 | 'target': (t + k * y) % modulus, 53 | 'k': k, 54 | 'y': y 55 | }) 56 | -------------------------------------------------------------------------------- /examples/plus_equal_product_test.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import itertools 3 | import random 4 | 5 | import pytest 6 | 7 | import quantumpseudocode as qp 8 | from .plus_equal_product import * 9 | 10 | 11 | methods = [ 12 | plus_equal_product_builtin, 13 | plus_equal_product_iter_classical, 14 | plus_equal_product_iter_quantum, 15 | plus_equal_product_single_lookup, 16 | plus_equal_product_windowed_2, 17 | plus_equal_product_windowed_4, 18 | plus_equal_product_windowed_lg, 19 | ] 20 | 21 | cases = [ 22 | {'n': 10, 't': 5, 'k': 7, 'y': 13}, 23 | {'n': 4, 't': 5, 'k': 7, 'y': 13}, 24 | {'n': 1, 't': 1, 'k': 1, 'y': 1}, 25 | {'k': 847561784, 'n': 30, 't': 771685281, 'y': 541683290}, 26 | {'k': 2, 'n': 2, 't': 0, 'y': 2}, 27 | {'k': 2, 'n': 2, 't': 1, 'y': 2}, 28 | { 29 | 'n': 30, 30 | 't': random.randint(0, 2**30-1), 31 | 'k': random.randint(0, 2**30-1), 32 | 'y': random.randint(0, 2**30-1) 33 | }, 34 | ] 35 | 36 | 37 | @pytest.mark.parametrize( 38 | 'method,case', 39 | itertools.product(methods, cases) 40 | ) 41 | def test_correct_result(method, case): 42 | n = case['n'] 43 | t = case['t'] 44 | k = case['k'] 45 | y = case['y'] 46 | 47 | final_state = qp.testing.sim_call( 48 | method, 49 | target=qp.IntBuf.raw(val=t, length=n), 50 | k=k, 51 | y=y) 52 | assert final_state == qp.ArgsAndKwargs([], { 53 | 'target': (t + k * y) % 2**n, 54 | 'k': k, 55 | 'y': y 56 | }) 57 | -------------------------------------------------------------------------------- /examples/times_equal.py: -------------------------------------------------------------------------------- 1 | from quantumpseudocode import * 2 | 3 | 4 | def times_equal_builtin(target: Quint, k: int): 5 | assert k & 1 6 | target *= k 7 | 8 | 9 | def times_equal_classic(target: Quint, k: int): 10 | assert k % 2 == 1 # Even factors aren't reversible. 11 | k %= 2**len(target) # Normalize factor. 12 | for i in range(len(target))[::-1]: 13 | target[i + 1:] += (k >> 1) & controlled_by(target[i]) 14 | 15 | 16 | def times_equal_windowed(target: Quint, k: int, window: int): 17 | assert k % 2 == 1 # Even factors aren't reversible. 18 | k %= 2**len(target) # Normalize factor. 19 | if k == 1: 20 | return 21 | table = LookupTable([ 22 | (j * k) >> window 23 | for j in range(2**window) 24 | ]) 25 | for i in range(0, len(target), window)[::-1]: 26 | w = target[i:i + window] 27 | target[i + window:] += table[w] 28 | 29 | # Recursively fix up the window. 30 | times_equal_windowed(w, k, window=1) 31 | 32 | 33 | def times_equal_windowed_2(target: Quint, k: int): 34 | times_equal_windowed(target, k, 2) 35 | 36 | 37 | def times_equal_windowed_4(target: Quint, k: int): 38 | times_equal_windowed(target, k, 4) 39 | 40 | 41 | def times_equal_windowed_lg(target: Quint, k: int): 42 | w = max(1, ceil_lg2(len(target))) 43 | times_equal_windowed(target, k, w) 44 | -------------------------------------------------------------------------------- /examples/times_equal_exp_mod.py: -------------------------------------------------------------------------------- 1 | from quantumpseudocode import * 2 | 3 | 4 | def times_equal_exp_mod(target: QuintMod, 5 | k: int, 6 | e: Quint, 7 | e_window: int, 8 | m_window: int): 9 | """Performs `target *= k**e`, modulo the target's modulus.""" 10 | N = target.modulus 11 | ki = modular_multiplicative_inverse(k, N) 12 | assert ki is not None 13 | 14 | a = target 15 | b = qalloc(modulus=N) 16 | 17 | for i in range(0, len(e), e_window): 18 | ei = e[i:i + e_window] 19 | 20 | # Exponent-indexed factors and inverse factors. 21 | kes = [pow(k, 2**i * x, N) 22 | for x in range(2**e_window)] 23 | kes_inv = [modular_multiplicative_inverse(x, N) 24 | for x in kes] 25 | 26 | # Perform b += a * k_e (mod modulus). 27 | # Maps (x, 0) into (x, x*k_e). 28 | for j in range(0, len(a), m_window): 29 | mi = a[j:j + m_window] 30 | table = LookupTable( 31 | [(ke * f * 2**j) % N 32 | for f in range(2**len(mi))] 33 | for ke in kes) 34 | b += table[ei, mi] 35 | 36 | # Perform a -= b * inv(k_e) (mod modulus). 37 | # Maps (x, x*k_e) into (0, x*k_e). 38 | for j in range(0, len(a), m_window): 39 | mi = b[j:j + m_window] 40 | table = LookupTable( 41 | [(ke_inv * f * 2**j) % N 42 | for f in range(2**len(mi))] 43 | for ke_inv in kes_inv) 44 | a -= table[ei, mi] 45 | 46 | # Relabelling swap. Maps (0, x*k_e) into (x*k_e, 0). 47 | a, b = b, a 48 | 49 | # Xor swap result into correct register if needed. 50 | if a is not target: 51 | swap(a, b) 52 | a, b = b, a 53 | qfree(b) 54 | 55 | 56 | def times_equal_exp_mod_window_1_1(target: QuintMod, 57 | k: int, 58 | e: Quint): 59 | return times_equal_exp_mod(target, k, e, 1, 1) 60 | 61 | 62 | def times_equal_exp_mod_window_2_3(target: QuintMod, 63 | k: int, 64 | e: Quint): 65 | return times_equal_exp_mod(target, k, e, 2, 3) 66 | -------------------------------------------------------------------------------- /examples/times_equal_exp_mod_test.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import itertools 3 | import random 4 | 5 | import pytest 6 | 7 | import quantumpseudocode as qp 8 | from .times_equal_exp_mod import * 9 | 10 | 11 | methods = [ 12 | times_equal_exp_mod_window_1_1, 13 | times_equal_exp_mod_window_2_3, 14 | ] 15 | 16 | cases = [ 17 | {'N': 11, 'k': 5, 'e': 7, 't': 1}, 18 | {'N': 213, 'k': 29, 'e': 1111, 't': 22}, 19 | ] 20 | 21 | 22 | @pytest.mark.parametrize( 23 | 'method,case', 24 | itertools.product(methods, cases) 25 | ) 26 | def test_correct_result(method, case): 27 | modulus = case['N'] 28 | t = case['t'] 29 | k = case['k'] 30 | e = case['e'] 31 | t %= modulus 32 | k %= modulus 33 | 34 | final_state = qp.testing.sim_call( 35 | method, 36 | target=qp.testing.ModInt(val=t, modulus=modulus), 37 | k=k, 38 | e=e) 39 | assert final_state == qp.ArgsAndKwargs([], { 40 | 'target': (t * k ** e) % modulus, 41 | 'k': k, 42 | 'e': e 43 | }) 44 | -------------------------------------------------------------------------------- /examples/times_equal_test.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import random 3 | 4 | import pytest 5 | 6 | import quantumpseudocode as qp 7 | from .times_equal import * 8 | 9 | 10 | methods = [ 11 | times_equal_builtin, 12 | times_equal_classic, 13 | times_equal_windowed_2, 14 | times_equal_windowed_4, 15 | times_equal_windowed_lg, 16 | ] 17 | 18 | cases = [ 19 | {'n': 5, 't': 2, 'k': 3}, 20 | { 21 | 'n': 10, 22 | 't': random.randint(0, 2**10-1), 23 | 'k': random.randint(0, 2**10-1) | 1, 24 | }, 25 | { 26 | 'n': 20, 27 | 't': random.randint(0, 2**20 - 1), 28 | 'k': random.randint(0, 2**20 - 1) | 1, 29 | }, 30 | ] 31 | 32 | 33 | @pytest.mark.parametrize( 34 | 'method,case', 35 | itertools.product(methods, cases) 36 | ) 37 | def test_correct_result(method, case): 38 | n = case['n'] 39 | t = case['t'] 40 | k = case['k'] 41 | 42 | final_state = qp.testing.sim_call( 43 | method, 44 | target=qp.IntBuf.raw(val=t, length=n), 45 | k=k) 46 | assert final_state == qp.ArgsAndKwargs([], { 47 | 'target': t * k % 2**n, 48 | 'k': k, 49 | }) 50 | -------------------------------------------------------------------------------- /quantumpseudocode/__init__.py: -------------------------------------------------------------------------------- 1 | from quantumpseudocode.sink import ( 2 | capture, 3 | CaptureLens, 4 | StartMeasurementBasedUncomputationResult, 5 | EmptyManager, 6 | RandomSim, 7 | Sink, 8 | ) 9 | 10 | from quantumpseudocode.buf import ( 11 | Buffer, 12 | IntBuf, 13 | RawConcatBuffer, 14 | RawIntBuffer, 15 | RawWindowBuffer, 16 | ) 17 | 18 | from quantumpseudocode.rvalue import ( 19 | BoolRValue, 20 | LookupTable, 21 | HeldRValueManager, 22 | hold, 23 | IntRValue, 24 | rval, 25 | RValue, 26 | QubitIntersection, 27 | QubitRValue, 28 | QuintRValue, 29 | ScaledIntRValue, 30 | ) 31 | 32 | from quantumpseudocode.lvalue import ( 33 | NamedQureg, 34 | RangeQureg, 35 | RawQureg, 36 | pad, 37 | pad_all, 38 | PaddedQureg, 39 | Qubit, 40 | Quint, 41 | QuintMod, 42 | Qureg, 43 | ) 44 | 45 | from quantumpseudocode.util import ( 46 | ArgParameter, 47 | ArgsAndKwargs, 48 | ceil_lg2, 49 | ceil_power_of_two, 50 | floor_lg2, 51 | floor_power_of_two, 52 | leading_zero_bit_count, 53 | modular_multiplicative_inverse, 54 | MultiWith, 55 | popcnt, 56 | little_endian_int, 57 | little_endian_bits, 58 | ccz_count, 59 | ) 60 | 61 | from quantumpseudocode.control import ( 62 | controlled_by, 63 | ControlledRValue, 64 | ControlledLValue, 65 | ) 66 | 67 | from quantumpseudocode.sim import ( 68 | Sim, 69 | ) 70 | 71 | from quantumpseudocode.log_cirq import ( 72 | LogCirqCircuit, 73 | CountNots, 74 | ) 75 | 76 | from quantumpseudocode.ops import ( 77 | semi_quantum, 78 | ClassicalSimState, 79 | ) 80 | 81 | from quantumpseudocode.arithmetic import ( 82 | IfLessThanRVal, 83 | QuintEqConstRVal, 84 | UnaryRValue, 85 | LookupRValue, 86 | measure, 87 | measurement_based_uncomputation, 88 | phase_flip, 89 | swap, 90 | ) 91 | 92 | from quantumpseudocode.qalloc import ( 93 | AllocArgs, 94 | qalloc, 95 | qfree, 96 | ReleaseQuregOperation, 97 | ) 98 | 99 | from quantumpseudocode.arithmetic_mod import ( 100 | do_plus_mod, 101 | ) 102 | 103 | import quantumpseudocode.testing as testing 104 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/__init__.py: -------------------------------------------------------------------------------- 1 | from .xor import ( 2 | do_xor, 3 | do_xor_const, 4 | ) 5 | 6 | from .add import ( 7 | do_addition, 8 | ) 9 | 10 | from .cmp import ( 11 | IfLessThanRVal, 12 | QuintEqConstRVal, 13 | do_if_less_than, 14 | ) 15 | 16 | from .mul import ( 17 | do_multiplication, 18 | ) 19 | 20 | from .mult_add import ( 21 | do_plus_product, 22 | ) 23 | 24 | from .measure import ( 25 | measure, 26 | measurement_based_uncomputation, 27 | ) 28 | 29 | from .lookup import ( 30 | LookupRValue, 31 | do_xor_lookup, 32 | del_xor_lookup, 33 | ) 34 | 35 | from .phase_flip import ( 36 | phase_flip, 37 | ) 38 | 39 | from .unary import ( 40 | UnaryRValue, 41 | ) 42 | 43 | from .swap import ( 44 | swap, 45 | ) 46 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/add.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | import quantumpseudocode as qp 4 | from quantumpseudocode.ops import semi_quantum 5 | 6 | 7 | def do_addition_classical(*, 8 | lvalue: qp.IntBuf, 9 | offset: int, 10 | carry_in: bool = False, 11 | forward: bool = True): 12 | if forward: 13 | lvalue += offset + carry_in 14 | else: 15 | lvalue -= offset + carry_in 16 | 17 | 18 | @semi_quantum(classical=do_addition_classical) 19 | def do_addition(*, 20 | control: qp.Qubit.Control = True, 21 | lvalue: qp.Quint, 22 | offset: qp.Quint.Borrowed, 23 | carry_in: qp.Qubit.Borrowed = False, 24 | forward: bool = True): 25 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 26 | assert isinstance(lvalue, qp.Quint) 27 | assert isinstance(offset, qp.Quint) 28 | assert isinstance(carry_in, qp.Qubit) 29 | 30 | out_len = len(lvalue) 31 | 32 | # Special cases. 33 | if out_len == 0: 34 | return 35 | if out_len == 1: 36 | if len(offset): 37 | lvalue[0] ^= offset[0] & control 38 | lvalue[0] ^= carry_in & control 39 | return 40 | 41 | if not forward: 42 | lvalue ^= -1 43 | 44 | with offset.hold_padded_to(out_len - 1) as offset: 45 | in_len = min(out_len, len(offset)) 46 | 47 | # Propagate carry. 48 | maj_sweep(lvalue, carry_in, offset) 49 | 50 | # Carry output. 51 | if out_len == in_len + 1: 52 | lvalue[in_len] ^= offset[in_len - 1] & control 53 | 54 | # Apply and uncompute carries. 55 | uma_sweep(lvalue, carry_in, offset, control) 56 | 57 | if not forward: 58 | lvalue ^= -1 59 | 60 | 61 | def maj_sweep(lvalue: Union[qp.Quint, List[qp.Qubit], qp.Qureg], 62 | carry: qp.Qubit, 63 | offset: Union[qp.Quint, List[qp.Qubit], qp.Qureg]): 64 | out_len = len(lvalue) 65 | carry_then_offset = [carry] + list(offset) 66 | in_len = min(out_len, len(offset)) 67 | 68 | for i in range(in_len): 69 | a = carry_then_offset[i] 70 | b = lvalue[i] 71 | c = offset[i] 72 | 73 | # Maj. 74 | a ^= c 75 | b ^= c 76 | c ^= a & b 77 | 78 | 79 | def uma_sweep(lvalue: Union[qp.Quint, List[qp.Qubit], qp.Qureg], 80 | carry: qp.Qubit, 81 | offset: Union[qp.Quint, List[qp.Qubit], qp.Qureg], 82 | controls: qp.QubitIntersection): 83 | out_len = len(lvalue) 84 | carry_then_offset = [carry] + list(offset) 85 | in_len = min(out_len, len(offset)) 86 | 87 | for i in range(in_len)[::-1]: 88 | a = carry_then_offset[i] 89 | b = lvalue[i] 90 | c = offset[i] 91 | 92 | # Uma. 93 | c ^= a & b 94 | b ^= a & controls 95 | b ^= c 96 | a ^= c 97 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/add_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | def test_plus_equal_gate_circuit(): 9 | with qp.Sim(enforce_release_at_zero=False): 10 | a = qp.qalloc(len=3, name='a') 11 | t = qp.qalloc(len=4, name='t') 12 | c = qp.qalloc(name='_c') 13 | with qp.LogCirqCircuit() as circuit: 14 | qp.arithmetic.do_addition(lvalue=t, offset=a, carry_in=c) 15 | 16 | cirq.testing.assert_has_diagram(circuit, r""" 17 | _c: -----X-------@---------------------------------------------------------------@---@-------X--- 18 | | | | | | 19 | a[0]: ---@---@---X---X-------@-----------------------------------@---@-------X---X---|---@---@--- 20 | | | | | | | | | | | 21 | a[1]: -------|---|---@---@---X---X-------@-------@---@-------X---X---|---@---@---|---|---|------- 22 | | | | | | | | | | | | | | | | 23 | a[2]: -------|---|-------|---|---@---@---X---@---X---|---@---@---|---|---|-------|---|---|------- 24 | | | | | | | | | | | | | | | | | 25 | t[0]: -------X---@-------|---|-------|---|---|---|---|---|-------|---|---|-------@---X---X------- 26 | | | | | | | | | | | | 27 | t[1]: -------------------X---@-------|---|---|---|---|---|-------@---X---X----------------------- 28 | | | | | | | 29 | t[2]: -------------------------------X---@---|---@---X---X--------------------------------------- 30 | | 31 | t[3]: ---------------------------------------X--------------------------------------------------- 32 | """, use_unicode_characters=False) 33 | 34 | 35 | def test_quantum_classical_consistent(): 36 | qp.testing.assert_semi_quantum_func_is_consistent( 37 | qp.arithmetic.do_addition, 38 | fuzz_space={ 39 | 'lvalue': lambda: qp.IntBuf.random(range(0, 6)), 40 | 'offset': lambda: random.randint(0, 511), 41 | 'carry_in': [False, True], 42 | 'forward': [False, True], 43 | }, 44 | fuzz_count=100) 45 | 46 | qp.testing.assert_semi_quantum_func_is_consistent( 47 | qp.arithmetic.do_addition, 48 | fixed=[{ 49 | 'lvalue': qp.IntBuf.raw(val=3, length=3), 50 | 'offset': 2, 51 | 'forward': True, 52 | }]) 53 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/cmp.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, Union, Callable 2 | 3 | import quantumpseudocode as qp 4 | from quantumpseudocode import semi_quantum 5 | from quantumpseudocode.rvalue import RValue 6 | 7 | 8 | def _forward_sweep(*, 9 | lhs: qp.Quint, 10 | rhs: qp.Quint, 11 | carry: qp.Qubit): 12 | assert len(lhs) == len(rhs) 13 | carry_then_rhs = [carry] + list(rhs) 14 | 15 | for i in range(len(lhs)): 16 | a = carry_then_rhs[i] 17 | b = rhs[i] 18 | c = lhs[i] 19 | 20 | c ^= a 21 | a ^= b 22 | b ^= a & c 23 | 24 | 25 | def _backward_sweep(lhs: qp.Quint, 26 | rhs: qp.Quint, 27 | carry: qp.Qubit): 28 | assert len(lhs) == len(rhs) 29 | carry_then_rhs = [carry] + list(rhs) 30 | 31 | for i in range(len(lhs))[::-1]: 32 | a = carry_then_rhs[i] 33 | b = rhs[i] 34 | c = lhs[i] 35 | 36 | b ^= a & c 37 | a ^= b 38 | c ^= a 39 | 40 | 41 | @semi_quantum 42 | def do_if_less_than(*, 43 | control: 'qp.Qubit.Control' = True, 44 | lhs: 'qp.Quint.Borrowed', 45 | rhs: 'qp.Quint.Borrowed', 46 | or_equal: 'qp.Qubit.Borrowed' = False, 47 | effect: Callable[['qp.QubitIntersection'], None]): 48 | """Performs an effect controlled by a comparison between two quantum integers.""" 49 | n = max(len(lhs), len(rhs)) 50 | if n == 0: 51 | effect(control & or_equal) 52 | return 53 | 54 | with qp.pad_all(lhs, rhs, min_len=n) as (pad_lhs, pad_rhs): 55 | _forward_sweep(lhs=pad_lhs, rhs=pad_rhs, carry=or_equal) 56 | effect(control & pad_rhs[-1]) 57 | _backward_sweep(lhs=pad_lhs, rhs=pad_rhs, carry=or_equal) 58 | 59 | 60 | class QuintEqConstRVal(RValue[bool]): 61 | """An equality comparison between a quantum integer and a classical integer.""" 62 | 63 | def __init__(self, 64 | lhs: 'qp.Quint', 65 | rhs: int, 66 | invert: bool = False): 67 | self.lhs = lhs 68 | self.rhs = rhs 69 | self.invert = invert 70 | 71 | def alloc_storage_location(self, name: Optional[str] = None): 72 | return qp.qalloc(name=name) 73 | 74 | def init_storage_location(self, 75 | location: Any, 76 | controls: 'qp.QubitIntersection'): 77 | if self.rhs & (-1 << len(self.lhs)): 78 | location ^= self.invert 79 | return 80 | self.lhs ^= ~self.rhs 81 | location ^= qp.QubitIntersection(tuple(self.lhs)) & controls 82 | location ^= self.invert 83 | self.lhs ^= ~self.rhs 84 | 85 | def clear_storage_location(self, 86 | location: Any, 87 | controls: 'qp.QubitIntersection'): 88 | if self.rhs & (-1 << len(self.lhs)): 89 | with qp.measurement_based_uncomputation(location) as b: 90 | if b: 91 | qp.phase_flip(self.invert) 92 | return 93 | with qp.measurement_based_uncomputation(location) as b: 94 | if b: 95 | self.lhs ^= ~self.rhs 96 | qp.phase_flip(qp.QubitIntersection(tuple(self.lhs)) & controls & b) 97 | qp.phase_flip(self.invert) 98 | self.lhs ^= ~self.rhs 99 | 100 | def __str__(self): 101 | return f'{self.lhs} == {self.rhs}' 102 | 103 | def __repr__(self): 104 | return f'qp.QuintEqConstRVal({self.lhs!r}, {self.rhs!r})' 105 | 106 | 107 | class IfLessThanRVal(RValue[bool]): 108 | """A comparison operation between two quantum integers.""" 109 | 110 | def __init__(self, 111 | lhs: Union[int, qp.Quint, RValue[int]], 112 | rhs: Union[int, qp.Quint, RValue[int]], 113 | or_equal: Union[bool, qp.Qubit, RValue[bool]]): 114 | self.lhs = lhs 115 | self.rhs = rhs 116 | self.or_equal = or_equal 117 | 118 | def alloc_storage_location(self, name: Optional[str] = None): 119 | return qp.qalloc(name=name) 120 | 121 | def init_storage_location(self, 122 | location: 'qp.Qubit', 123 | controls: 'qp.QubitIntersection'): 124 | location ^= self & qp.controlled_by(controls) 125 | 126 | def clear_storage_location(self, 127 | location: 'qp.Qubit', 128 | controls: 'qp.QubitIntersection'): 129 | with qp.measurement_based_uncomputation(location) as b: 130 | c = controls & b 131 | if c.bit: 132 | do_if_less_than( 133 | control=c, 134 | lhs=self.lhs, 135 | rhs=self.rhs, 136 | or_equal=self.or_equal, 137 | effect=qp.phase_flip) 138 | 139 | def __rixor__(self, other): 140 | other, controls = qp.ControlledLValue.split(other) 141 | if controls == qp.QubitIntersection.NEVER: 142 | return other 143 | 144 | if isinstance(other, qp.Qubit): 145 | do_if_less_than( 146 | control=controls, 147 | lhs=self.lhs, 148 | rhs=self.rhs, 149 | or_equal=self.or_equal, 150 | effect=other.__ixor__) 151 | return other 152 | 153 | return NotImplemented 154 | 155 | def __str__(self): 156 | if isinstance(self.or_equal, qp.BoolRValue): 157 | return '{} {} {}'.format( 158 | self.lhs, 159 | '<=' if self.or_equal else '<', 160 | self.rhs) 161 | 162 | return f'{self.lhs} <= {self.rhs} if {self.or_equal} else {self.lhs} < {self.rhs}' 163 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/cmp_test.py: -------------------------------------------------------------------------------- 1 | import cirq 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | def test_cmp(): 7 | with qp.Sim(): 8 | with qp.qalloc(len=4) as t: 9 | t.init(5) 10 | for k in range(-5, 30): 11 | assert qp.measure(t >= k) == (5 >= k) 12 | assert qp.measure(t > k) == (5 > k) 13 | assert qp.measure(t < k) == (5 < k) 14 | assert qp.measure(k < t) == (k < 5) 15 | assert qp.measure(t <= k) == (5 <= k) 16 | assert qp.measure(t, reset=True) == 5 17 | 18 | 19 | def test_eq(): 20 | with qp.Sim(): 21 | with qp.qalloc(len=4) as t: 22 | t.init(5) 23 | for k in range(-2, 60): 24 | assert qp.measure(t == k) == (k == 5) 25 | assert qp.measure(t, reset=True) == 5 26 | 27 | 28 | def test_neq(): 29 | with qp.Sim(): 30 | with qp.qalloc(len=4) as t: 31 | t.init(5) 32 | for k in range(-2, 60): 33 | with qp.hold(t != k) as q: 34 | assert qp.measure(q) == (k != 5) 35 | assert qp.measure(t, reset=True) == 5 36 | 37 | 38 | def test_eq_circuit(): 39 | with qp.Sim(enforce_release_at_zero=False, phase_fixup_bias=True): 40 | with qp.LogCirqCircuit() as circuit: 41 | with qp.qalloc(len=4, name='lhs') as lhs: 42 | with qp.hold(lhs == 5, name='target'): 43 | pass 44 | 45 | cirq.testing.assert_has_diagram(circuit, r""" 46 | lhs[0]: ---alloc---------------@-----------------@-----------------------release--- 47 | | | | | 48 | lhs[1]: ---alloc-----------X---@---X---------X---@---X-------------------release--- 49 | | | | | | | | | 50 | lhs[2]: ---alloc-----------|---@---|---------|---@---|-------------------release--- 51 | | | | | | | | | 52 | lhs[3]: ---alloc-----------X---@---X---------X---Z---X-------------------release--- 53 | | 54 | target: -----------alloc-------X-------Mxc---------------cxM---release------------- 55 | """, use_unicode_characters=False) 56 | 57 | 58 | def test_if_less_than_then_circuit(): 59 | with qp.Sim(enforce_release_at_zero=False): 60 | lhs = qp.qalloc(len=4, name='lhs') 61 | rhs = qp.qalloc(len=4, name='rhs') 62 | c = qp.qalloc(name='_or_eq') 63 | t = qp.qalloc(name='t') 64 | with qp.LogCirqCircuit() as circuit: 65 | qp.arithmetic.do_if_less_than( 66 | lhs=lhs, 67 | rhs=rhs, 68 | or_equal=c, 69 | effect=t.__ixor__) 70 | cirq.testing.assert_has_diagram(circuit, r""" 71 | _or_eq: ---@---X---@-------------------------------------------------------------------------------@---X---@--- 72 | | | | | | | 73 | lhs[0]: ---X---|---@-------------------------------------------------------------------------------@---|---X--- 74 | | | | | 75 | lhs[1]: -------|---|---X-------@-------------------------------------------------------@-------X---|---|------- 76 | | | | | | | | | 77 | lhs[2]: -------|---|---|-------|---X-------@-------------------------------@-------X---|-------|---|---|------- 78 | | | | | | | | | | | | | 79 | lhs[3]: -------|---|---|-------|---|-------|---X-------@-------@-------X---|-------|---|-------|---|---|------- 80 | | | | | | | | | | | | | | | | | 81 | rhs[0]: -------@---X---@---X---@---|-------|---|-------|-------|-------|---|-------|---@---X---@---X---@------- 82 | | | | | | | | | | | | | 83 | rhs[1]: -------------------@---X---@---X---@---|-------|-------|-------|---@---X---@---X---@------------------- 84 | | | | | | | | | 85 | rhs[2]: -------------------------------@---X---@---X---@-------@---X---@---X---@------------------------------- 86 | | | | | 87 | rhs[3]: -------------------------------------------@---X---@---X---@------------------------------------------- 88 | | 89 | t: --------------------------------------------------------X--------------------------------------------------- 90 | """, use_unicode_characters=False) 91 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/coherent_mul.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def init_mul(*, 5 | factor1: qp.Quint, 6 | factor2: qp.Quint, 7 | clean_out: qp.Quint): 8 | """Initializes a zero'd quint to store the product of two quints.""" 9 | n = len(factor1) 10 | m = len(factor2) 11 | for k in range(n): 12 | clean_out[k:k+m+1] += factor2 & qp.controlled_by(factor1[k]) 13 | return clean_out 14 | 15 | 16 | def init_square(*, 17 | factor: qp.Quint, 18 | clean_out: qp.Quint): 19 | """Initializes a zero'd quint to store the square of a quint.""" 20 | n = len(factor) 21 | zero = qp.qalloc(name='_sqr_zero') 22 | for k in range(n): 23 | rval = factor[k+1:] & qp.controlled_by(factor[k]) 24 | with qp.hold(rval, name='_sqr_offset') as partial_offset: 25 | offset = qp.Quint(qp.RawQureg([ 26 | factor[k], 27 | zero, 28 | *partial_offset.qureg 29 | ])) 30 | clean_out[2*k:2*k+len(offset)+1] += offset 31 | qp.qfree(zero) 32 | return clean_out 33 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/coherent_mul_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from .coherent_mul import init_mul, init_square 7 | 8 | 9 | def assert_squares_correctly(n1: int, n2: int, v: int): 10 | final_state = qp.testing.sim_call( 11 | init_square, 12 | factor=qp.IntBuf.raw(val=v, length=n1), 13 | clean_out=qp.IntBuf.raw(val=0, length=n2)) 14 | 15 | assert final_state == qp.ArgsAndKwargs([], { 16 | 'factor': v, 17 | 'clean_out': v**2 % 2**n2 18 | }) 19 | 20 | 21 | def assert_multiplies_correctly(n1: int, n2: int, n3: int, v1: int, v2: int): 22 | final_state = qp.testing.sim_call( 23 | init_mul, 24 | factor1=qp.IntBuf.raw(val=v1, length=n1), 25 | factor2=qp.IntBuf.raw(val=v2, length=n2), 26 | clean_out=qp.IntBuf.raw(val=0, length=n3)) 27 | 28 | assert final_state == qp.ArgsAndKwargs([], { 29 | 'factor1': v1, 30 | 'factor2': v2, 31 | 'clean_out': v1 * v2 % 2**n3 32 | }) 33 | 34 | 35 | def test_init_square(): 36 | for _ in range(10): 37 | n1 = random.randint(0, 20) 38 | n2 = random.randint(0, 3*n1) 39 | v = random.randint(0, 2**n1 - 1) 40 | assert_squares_correctly(n1, n2, v) 41 | 42 | 43 | def test_init_mul(): 44 | for _ in range(10): 45 | n1 = random.randint(0, 20) 46 | n2 = random.randint(0, 20) 47 | n3 = random.randint(0, n1 + n2 + 5) 48 | v1 = random.randint(0, 2**n1 - 1) 49 | v2 = random.randint(0, 2**n2 - 1) 50 | assert_multiplies_correctly(n1, n2, n3, v1, v2) 51 | 52 | 53 | def test_init_square_circuit(): 54 | with qp.Sim(phase_fixup_bias=True, enforce_release_at_zero=False): 55 | factor = qp.qalloc(len=2, name='f') 56 | out = qp.qalloc(len=4, name='s') 57 | with qp.LogCirqCircuit() as circuit: 58 | init_square(factor=factor, clean_out=out) 59 | cirq.testing.assert_has_diagram(circuit, r""" 60 | _do_addition_carry_in: -----------------------alloc---X-------@---------------------------------------------------------------@---@-------X---Mxc---cxM---release-----------------alloc---X-------@-------------------------------@---@-------X---Mxc---cxM---release------------- 61 | | | | | | | | | | | 62 | _sqr_offset: ---------------------alloc---X-----------|-------|---------------@---@---X---@---X-------@---@-------------------|---|-------|-------------------------X---release-----------|-------|-------------------------------|---|-------|----------------------------------- 63 | | | | | | | | | | | | | | | | | | | | 64 | _sqr_zero: ---------------alloc-----------|-----------|-------|---@---@---X---X---|---@---|---@---@---|---X---X-------@---@---|---|-------|-------------------------|---------------------|-------|---@---@---X---X-------@---@---|---|-------|-------------------------release--- 65 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 66 | f[0]: ------------------------------------@-----------@---@---X---X---|---@-------|---|---|---|---|---|-------@---@---|---X---X---|---@---@-------------------------@---------------------|-------|---|---|---|---|-------|---|---|---|-------|----------------------------------- 67 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 68 | f[1]: ------------------------------------@---------------|---|-------|---|-------|---|---|---|---|---|-------|---|---|-------|---|---|-----------------------------@---------------------@---@---X---X---|---@---@---@---|---X---X---|---@---@----------------------------------- 69 | | | | | | | | | | | | | | | | | | | | | | | | | | | 70 | s[0]: ----------------------------------------------------X---@-------|---|-------|---|---|---|---|---|-------|---|---|-------@---X---X-------------------------------------------------------|---|-------|---|---|---|---|-------|---|---|--------------------------------------- 71 | | | | | | | | | | | | | | | | | | | | | | 72 | s[1]: ----------------------------------------------------------------X---@-------|---|---|---|---|---|-------@---X---X-----------------------------------------------------------------------|---|-------|---|---|---|---|-------|---|---|--------------------------------------- 73 | | | | | | | | | | | | | | | | | 74 | s[2]: ----------------------------------------------------------------------------X---@---|---@---X---X---------------------------------------------------------------------------------------X---@-------|---|---|---|---|-------@---X---X--------------------------------------- 75 | | | | | | | 76 | s[3]: ------------------------------------------------------------------------------------X---------------------------------------------------------------------------------------------------------------X---@---@---X---X------------------------------------------------------- 77 | """, use_unicode_characters=False) 78 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/lookup.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple, Iterable, List 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from quantumpseudocode.ops import semi_quantum 7 | 8 | 9 | def do_classical_xor_lookup(sim_state: 'qp.ClassicalSimState', 10 | *, 11 | lvalue: 'qp.IntBuf', 12 | table: 'qp.LookupTable', 13 | address: int, 14 | phase_instead_of_toggle: bool = False): 15 | mask = table.values[address] 16 | assert 0 <= address < len(table) 17 | if phase_instead_of_toggle: 18 | if qp.popcnt(int(lvalue) & mask) & 1: 19 | sim_state.phase_degrees += 180 20 | else: 21 | lvalue ^= mask 22 | 23 | 24 | @semi_quantum(classical=do_classical_xor_lookup, alloc_prefix='_qrom_') 25 | def do_xor_lookup(*, 26 | lvalue: 'qp.Quint', 27 | table: 'qp.LookupTable', 28 | address: 'qp.Quint.Borrowed', 29 | phase_instead_of_toggle: bool = False, 30 | control: 'qp.Qubit.Control' = True): 31 | assert isinstance(lvalue, qp.Quint) 32 | assert isinstance(address, qp.Quint) 33 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 34 | table = table[:1 << len(address)] 35 | address = address[:qp.ceil_lg2(len(table))] 36 | 37 | # Base case: single distinct value in table. 38 | if all(e == table[0] for e in table): 39 | if phase_instead_of_toggle: 40 | for k in range(len(lvalue)): 41 | if table[0] & (1 << k): 42 | qp.phase_flip(control & lvalue[k]) 43 | else: 44 | lvalue ^= table[0] & qp.controlled_by(control) 45 | return 46 | 47 | # Recursive case: divide and conquer. 48 | high_bit = address[-1] 49 | rest = address[:-1] 50 | h = 1 << (len(address) - 1) 51 | low_table = table[:h] 52 | high_table = table[h:] 53 | with qp.hold(control & high_bit, name='_lookup_prefix') as q: 54 | # Do lookup for half of table where high_bit is 0. 55 | q ^= control # Flip q to storing 'controls & ~high_bit'. 56 | do_xor_lookup(lvalue=lvalue, 57 | table=low_table, 58 | address=rest, 59 | phase_instead_of_toggle=phase_instead_of_toggle, 60 | control=q) 61 | q ^= control 62 | 63 | # Do lookup for half of table where high_bit is 1. 64 | do_xor_lookup(lvalue=lvalue, 65 | table=high_table, 66 | address=rest, 67 | phase_instead_of_toggle=phase_instead_of_toggle, 68 | control=q) 69 | 70 | 71 | @semi_quantum(alloc_prefix='_qrom_', classical=do_classical_xor_lookup) 72 | def del_xor_lookup(*, 73 | lvalue: 'qp.Quint', 74 | table: 'qp.LookupTable', 75 | address: 'qp.Quint.Borrowed', 76 | control: 'qp.Qubit.Control' = True): 77 | """Uncomputes a table lookup using measurement based uncomputation.""" 78 | assert isinstance(lvalue, qp.Quint) 79 | assert isinstance(address, qp.Quint) 80 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 81 | table = table[:1 << len(address)] 82 | address = address[:qp.ceil_lg2(len(table))] 83 | 84 | if all(e == table[0] for e in table): 85 | qp.IntRValue(table[0]).clear_storage_location(lvalue, control) 86 | return 87 | 88 | split = min( 89 | qp.floor_lg2(len(lvalue)), # Point of no-more-workspace-available. 90 | len(address) // 2 # Point of optimal operation count. 91 | ) 92 | low = address[:split] 93 | high = address[split:] 94 | 95 | with qp.measurement_based_uncomputation(lvalue) as result: 96 | # Infer whether or not each address has a phase flip. 97 | raw_fixups = [bool(qp.popcnt(result & table[k]) & 1) 98 | for k in range(len(table))] 99 | fixup_table = qp.LookupTable(_chunk_bits(raw_fixups, 1 << split)) 100 | 101 | # Fix address phase flips using a smaller table lookup. 102 | unary_storage = lvalue[:1 << split] 103 | unary_storage.init(1 << low) 104 | do_xor_lookup( 105 | lvalue=unary_storage, 106 | table=fixup_table, 107 | address=high, 108 | phase_instead_of_toggle=True, 109 | control=control) 110 | unary_storage.clear(1 << low) 111 | 112 | 113 | def _chunk_bits(bits: List[bool], size: int) -> List[int]: 114 | return [ 115 | qp.little_endian_int(bits[k:k + size]) 116 | for k in range(0, len(bits), size) 117 | ] 118 | 119 | 120 | class LookupRValue(qp.RValue[int]): 121 | """Represents the temporary result of a table lookup.""" 122 | 123 | def __init__(self, table: qp.LookupTable, address: 'qp.Quint'): 124 | # Drop high bits that would place us beyond the range of the table. 125 | max_address_len = qp.ceil_lg2(len(table)) 126 | # Drop inaccessible parts of table. 127 | max_table_len = 1 << len(address) 128 | 129 | self.table = table[:max_table_len] 130 | self.address = address[:max_address_len] 131 | 132 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 133 | address = self.address.resolve(sim_state, False) 134 | return self.table.values[address] 135 | 136 | def __rixor__(self, other): 137 | other, controls = qp.ControlledLValue.split(other) 138 | if controls == qp.QubitIntersection.NEVER: 139 | return other 140 | 141 | if isinstance(other, qp.Quint): 142 | qp.arithmetic.do_xor_lookup( 143 | lvalue=other, 144 | table=self.table, 145 | address=self.address, 146 | phase_instead_of_toggle=False, 147 | control=controls) 148 | return other 149 | 150 | return NotImplemented 151 | 152 | def alloc_storage_location(self, name: Optional[str] = None): 153 | return qp.qalloc(len=self.table.output_len(), name=name) 154 | 155 | def init_storage_location(self, 156 | location: 'qp.Quint', 157 | controls: 'qp.QubitIntersection'): 158 | do_xor_lookup( 159 | lvalue=location, 160 | table=self.table, 161 | address=self.address, 162 | phase_instead_of_toggle=False, 163 | control=controls) 164 | 165 | def clear_storage_location(self, 166 | location: 'qp.Quint', 167 | controls: 'qp.QubitIntersection'): 168 | del_xor_lookup( 169 | lvalue=location, 170 | table=self.table, 171 | address=self.address, 172 | control=controls) 173 | 174 | def __str__(self): 175 | return 'T(len={})[{}]'.format(len(self.table), self.address) 176 | 177 | def __repr__(self): 178 | return 'qp.LookupRValue({!r}, {!r})'.format(self.table, self.address) 179 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/measure.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import List, Union, Callable, Any, Optional, TypeVar, Generic, overload, ContextManager, Tuple 3 | 4 | import quantumpseudocode as qp 5 | from quantumpseudocode import sink 6 | 7 | T = TypeVar('T') 8 | 9 | 10 | @overload 11 | def measure(val: qp.RValue[T], *, reset: bool = False) -> T: 12 | pass 13 | 14 | 15 | @overload 16 | def measure(val: qp.Quint, *, reset: bool = False) -> int: 17 | pass 18 | 19 | 20 | @overload 21 | def measure(val: qp.Qubit, *, reset: bool = False) -> bool: 22 | pass 23 | 24 | 25 | @overload 26 | def measure(val: qp.Qureg, *, reset: bool = False) -> List[bool]: 27 | pass 28 | 29 | 30 | def measure(val: Union[qp.RValue[T], qp.Quint, qp.Qureg, qp.Qubit], 31 | *, 32 | reset: bool = False) -> Union[bool, int, List[bool], T]: 33 | if isinstance(val, qp.RValue): 34 | with qp.hold(val) as target: 35 | return measure(target) 36 | 37 | if isinstance(val, qp.Qubit): 38 | qureg = val.qureg 39 | wrap = bool 40 | elif isinstance(val, qp.Qureg): 41 | qureg = val 42 | wrap = lambda e: qp.little_endian_bits(e, len(val)) 43 | elif isinstance(val, (qp.Quint, qp.QuintMod)): 44 | qureg = val.qureg 45 | wrap = lambda e: e 46 | else: 47 | raise NotImplementedError(f"Don't know how to measure {val!r}.") 48 | 49 | result = sink.global_sink.do_measure(qureg, reset) 50 | return wrap(result) 51 | 52 | 53 | @overload 54 | def measurement_based_uncomputation(val: qp.Quint) -> ContextManager[int]: 55 | pass 56 | 57 | 58 | @overload 59 | def measurement_based_uncomputation(val: qp.Qubit) -> ContextManager[bool]: 60 | pass 61 | 62 | 63 | @overload 64 | def measurement_based_uncomputation(val: qp.Qureg) -> ContextManager[Tuple[bool, ...]]: 65 | pass 66 | 67 | 68 | def measurement_based_uncomputation( 69 | val: Union[qp.Qubit, qp.Quint, qp.Qureg]) -> ContextManager[Union[int, bool, Tuple[bool, ...]]]: 70 | if isinstance(val, qp.Qubit): 71 | return MeasurementBasedUncomputationContext(val.qureg, bool) 72 | 73 | if isinstance(val, qp.Qureg): 74 | return MeasurementBasedUncomputationContext(val, lambda e: qp.little_endian_bits(e, len(val))) 75 | 76 | if isinstance(val, (qp.Quint, qp.QuintMod)): 77 | return MeasurementBasedUncomputationContext(val.qureg, lambda e: e) 78 | 79 | raise NotImplementedError(f"Don't know how to perform measurement based uncomputation on {val!r}") 80 | 81 | 82 | class MeasurementBasedUncomputationContext(Generic[T]): 83 | def __init__(self, qureg: qp.Qureg, interpret: Callable[[int], T]): 84 | self.qureg = qureg 85 | self.interpret = interpret 86 | self._start_result = None 87 | 88 | def __bool__(self): 89 | raise ValueError("Used `qp.measurement_based_uncomputation` like a value instead of like a context manager.\n" 90 | "Correct usage:\n" 91 | "\n" 92 | " with qp.measurement_based_uncomputation(qureg) as x_basis_result:\n" 93 | " # Undo phase kickbacks caused by the measurement.\n" 94 | " ...\n" 95 | "\n") 96 | 97 | def __enter__(self) -> T: 98 | assert self._start_result is None 99 | self._start_result = sink.global_sink.do_start_measurement_based_uncomputation(self.qureg) 100 | return self.interpret(self._start_result.measurement) 101 | 102 | def __exit__(self, exc_type, exc_val, exc_tb): 103 | assert self._start_result is not None 104 | sink.global_sink.do_end_measurement_based_uncomputation( 105 | self.qureg, self._start_result) 106 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/mul.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | from quantumpseudocode.ops import semi_quantum 3 | 4 | 5 | def do_multiplication_classical(*, 6 | lvalue: qp.IntBuf, 7 | factor: int, 8 | forward: bool = True): 9 | assert factor % 2 == 1 10 | if forward: 11 | lvalue *= factor 12 | else: 13 | lvalue *= qp.modular_multiplicative_inverse(factor, 2**len(lvalue)) 14 | 15 | 16 | @semi_quantum(alloc_prefix='_mul_', classical=do_multiplication_classical) 17 | def do_multiplication(*, 18 | control: qp.Qubit.Control = True, 19 | lvalue: qp.Quint, 20 | factor: int, 21 | forward: bool = True): 22 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 23 | assert isinstance(lvalue, qp.Quint) 24 | assert isinstance(factor, int) 25 | assert factor % 2 == 1 26 | if forward: 27 | for i in range(len(lvalue))[::-1]: 28 | c = control & lvalue[i] 29 | lvalue[i+1:] += (factor >> 1) & qp.controlled_by(c) 30 | else: 31 | for i in range(len(lvalue)): 32 | c = control & lvalue[i] 33 | lvalue[i+1:] -= (factor >> 1) & qp.controlled_by(c) 34 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/mul_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import pytest 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | @pytest.mark.parametrize('case', [ 9 | {'n': 5, 't': 2, 'k': 3}, 10 | { 11 | 'n': 10, 12 | 't': random.randint(0, 2**10-1), 13 | 'k': random.randint(0, 2**10-1) | 1, 14 | }, 15 | { 16 | 'n': 20, 17 | 't': random.randint(0, 2**20 - 1), 18 | 'k': random.randint(0, 2**20 - 1) | 1, 19 | }, 20 | ]) 21 | def test_correct_result(case): 22 | n = case['n'] 23 | t = case['t'] 24 | k = case['k'] 25 | 26 | def mul(target: qp.Quint, k: int): 27 | target *= k 28 | 29 | final_state = qp.testing.sim_call( 30 | mul, 31 | target=qp.IntBuf.raw(val=t, length=n), 32 | k=k) 33 | assert final_state == qp.ArgsAndKwargs([], { 34 | 'target': t * k % 2**n, 35 | 'k': k, 36 | }) 37 | 38 | 39 | def test_quantum_classical_consistent(): 40 | qp.testing.assert_semi_quantum_func_is_consistent( 41 | qp.arithmetic.do_multiplication, 42 | fuzz_space={ 43 | 'lvalue': lambda: qp.IntBuf.random(range(0, 6)), 44 | 'factor': lambda: random.randint(0, 31) * 2 + 1, 45 | 'forward': [False, True], 46 | }, 47 | fuzz_count=100) 48 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/mult_add.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | from quantumpseudocode.ops import semi_quantum 3 | 4 | 5 | def do_plus_product_classical(*, 6 | lvalue: qp.IntBuf, 7 | quantum_factor: qp.IntBuf, 8 | const_factor: int, 9 | forward: bool = True): 10 | if forward: 11 | lvalue += int(quantum_factor) * const_factor 12 | else: 13 | lvalue -= int(quantum_factor) * const_factor 14 | 15 | 16 | @semi_quantum(alloc_prefix='_plus_mul_', classical=do_plus_product_classical) 17 | def do_plus_product(*, 18 | control: qp.Qubit.Control = True, 19 | lvalue: qp.Quint, 20 | quantum_factor: qp.Quint.Borrowed, 21 | const_factor: int, 22 | forward: bool = True): 23 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 24 | assert isinstance(lvalue, qp.Quint) 25 | assert isinstance(quantum_factor, qp.Quint) 26 | assert isinstance(const_factor, int) 27 | for i, q in enumerate(quantum_factor): 28 | with qp.hold(const_factor & qp.controlled_by(q & control)) as offset: 29 | if forward: 30 | lvalue[i:] += offset 31 | else: 32 | lvalue[i:] -= offset 33 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/mult_add_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | def test_quantum_classical_consistent(): 7 | qp.testing.assert_semi_quantum_func_is_consistent( 8 | qp.arithmetic.do_plus_product, 9 | fuzz_space={ 10 | 'lvalue': lambda: qp.IntBuf.random(range(0, 6)), 11 | 'quantum_factor': lambda: random.randint(0, 99), 12 | 'const_factor': lambda: random.randint(0, 99), 13 | 'forward': [False, True], 14 | }, 15 | fuzz_count=100) 16 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/phase_flip.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import quantumpseudocode as qp 4 | from quantumpseudocode import sink 5 | 6 | 7 | def phase_flip(condition: 'Union[bool, qp.Qubit, qp.QubitIntersection, qp.RValue[bool]]' = True): 8 | if isinstance(condition, qp.QubitIntersection): 9 | sink.global_sink.do_phase_flip(condition) 10 | elif condition is False or condition == qp.QubitIntersection.NEVER: 11 | pass 12 | elif condition is True: 13 | phase_flip(qp.QubitIntersection.ALWAYS) 14 | elif isinstance(condition, qp.Qubit): 15 | phase_flip(qp.QubitIntersection((condition,))) 16 | elif isinstance(condition, (qp.Qubit, qp.RValue)): 17 | with qp.hold(condition) as q: 18 | qp.phase_flip(q) 19 | else: 20 | raise NotImplementedError("Unknown phase flip condition: {!r}".format(condition)) 21 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/swap.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | def swap(a: Union[qp.Qureg, qp.Quint, qp.QuintMod], 7 | b: Union[qp.Qureg, qp.Quint, qp.QuintMod]) -> None: 8 | if isinstance(a, qp.QuintMod): 9 | a = a[:] 10 | if isinstance(b, qp.QuintMod): 11 | b = b[:] 12 | a ^= b 13 | b ^= a 14 | a ^= b 15 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/unary.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Tuple, Optional 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | class UnaryRValue(qp.RValue[int]): 7 | """Represents the temporary result of a binary-to-unary conversion.""" 8 | 9 | def __init__(self, binary: 'qp.Quint'): 10 | self.binary = binary 11 | 12 | def __rixor__(self, other): 13 | other, controls = qp.ControlledLValue.split(other) 14 | if controls == qp.QubitIntersection.NEVER: 15 | return other 16 | 17 | if isinstance(other, qp.Quint): 18 | t = qp.LookupTable(1 << k for k in range(1 << len(self.binary))) 19 | other ^= t[self.binary] & qp.controlled_by(controls) 20 | return other 21 | 22 | return NotImplemented 23 | 24 | def alloc_storage_location(self, name: Optional[str] = None): 25 | return qp.qalloc(len=1 << len(self.binary), name=name) 26 | 27 | def init_storage_location(self, 28 | location: 'qp.Quint', 29 | controls: 'qp.QubitIntersection'): 30 | assert len(location) >= 1 << len(self.binary) 31 | location[0].init(controls) 32 | for i, q in enumerate(self.binary): 33 | s = 1 << i 34 | for j in range(s): 35 | location[j + s].init(location[j] & q) 36 | location[j] ^= location[j + s] 37 | 38 | def clear_storage_location(self, 39 | location: 'qp.Quint', 40 | controls: 'qp.QubitIntersection'): 41 | assert len(location) >= 1 << len(self.binary) 42 | for i, q in list(enumerate(self.binary))[::-1]: 43 | s = 1 << i 44 | for j in range(s)[::-1]: 45 | location[j] ^= location[j + s] 46 | location[j + s].clear(location[j] & q) 47 | location[0].clear(controls) 48 | 49 | def __str__(self): 50 | return '1 << {}'.format(self.binary) 51 | 52 | def __repr__(self): 53 | return 'qp.UnaryRValue({!r})'.format(self.binary) 54 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/xor.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | from quantumpseudocode.ops import semi_quantum 3 | from quantumpseudocode import sink 4 | 5 | 6 | def do_xor_const_classical(*, 7 | lvalue: qp.IntBuf, 8 | mask: int): 9 | lvalue ^= mask 10 | 11 | 12 | def do_xor_classical(*, 13 | lvalue: qp.IntBuf, 14 | mask: qp.IntBuf): 15 | lvalue ^= mask 16 | 17 | 18 | @semi_quantum(alloc_prefix='_xor_const_', classical=do_xor_const_classical) 19 | def do_xor_const(*, 20 | control: qp.Qubit.Control = True, 21 | lvalue: qp.Quint, 22 | mask: int): 23 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 24 | assert isinstance(lvalue, qp.Quint) 25 | assert isinstance(mask, int) 26 | targets = [q for i, q in enumerate(lvalue) if mask & (1 << i)] 27 | sink.global_sink.do_toggle(qp.RawQureg(targets), control) 28 | 29 | 30 | @semi_quantum(alloc_prefix='_xor_', classical=do_xor_classical) 31 | def do_xor(*, 32 | control: qp.Qubit.Control = True, 33 | lvalue: qp.Quint, 34 | mask: qp.Quint.Borrowed): 35 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 36 | assert isinstance(lvalue, qp.Quint) 37 | assert isinstance(mask, qp.Quint) 38 | for q, m in zip(lvalue, mask): 39 | sink.global_sink.do_toggle(q.qureg, control & m) 40 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic/xor_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | def test_quantum_classical_consistent(): 9 | qp.testing.assert_semi_quantum_func_is_consistent( 10 | qp.arithmetic.do_xor_const, 11 | fuzz_space={ 12 | 'lvalue': lambda: qp.IntBuf.random(range(0, 6)), 13 | 'mask': lambda: random.randint(0, 63), 14 | }, 15 | fuzz_count=100) 16 | 17 | qp.testing.assert_semi_quantum_func_is_consistent( 18 | qp.arithmetic.do_xor, 19 | fuzz_space={ 20 | 'lvalue': lambda: qp.IntBuf.random(range(0, 6)), 21 | 'mask': lambda: random.randint(0, 63), 22 | }, 23 | fuzz_count=100) 24 | 25 | 26 | def test_xor_equal_gate_circuit(): 27 | with qp.Sim(enforce_release_at_zero=False): 28 | with qp.LogCirqCircuit() as circuit: 29 | with qp.qalloc(len=3, name='a') as a: 30 | with qp.qalloc(len=4, name='t') as t: 31 | with qp.qalloc(name='_c') as c: 32 | qp.arithmetic.do_xor(lvalue=t, mask=a) 33 | qp.arithmetic.do_xor(lvalue=t, mask=a, control=c) 34 | 35 | cirq.testing.assert_has_diagram(circuit, r""" 36 | _c: ---------------------alloc---------------@---@---@---release----------------------- 37 | | | | 38 | a[0]: ---alloc-------------------@-----------@---|---|-----------------------release--- 39 | | | | | | | 40 | a[1]: ---alloc-------------------|---@-------|---@---|-----------------------release--- 41 | | | | | | | | 42 | a[2]: ---alloc-------------------|---|---@---|---|---@-----------------------release--- 43 | | | | | | | 44 | t[0]: -----------alloc-----------X---|---|---X---|---|-------------release------------- 45 | | | | | | | 46 | t[1]: -----------alloc---------------X---|-------X---|-------------release------------- 47 | | | | | 48 | t[2]: -----------alloc-------------------X-----------X-------------release------------- 49 | | | 50 | t[3]: -----------alloc---------------------------------------------release------------- 51 | """, use_unicode_characters=False) 52 | 53 | 54 | def test_xor_equal_gate_circuit_2(): 55 | with qp.Sim(enforce_release_at_zero=False): 56 | with qp.LogCirqCircuit() as circuit: 57 | with qp.qalloc(len=3, name='a') as a: 58 | with qp.qalloc(len=4, name='t') as t: 59 | with qp.qalloc(name='_c') as c: 60 | t ^= a 61 | t ^= a & qp.controlled_by(c) 62 | 63 | cirq.testing.assert_has_diagram(circuit, r""" 64 | _c: ---------------------alloc---------------@---@---@---release----------------------- 65 | | | | 66 | a[0]: ---alloc-------------------@-----------@---|---|-----------------------release--- 67 | | | | | | | 68 | a[1]: ---alloc-------------------|---@-------|---@---|-----------------------release--- 69 | | | | | | | | 70 | a[2]: ---alloc-------------------|---|---@---|---|---@-----------------------release--- 71 | | | | | | | 72 | t[0]: -----------alloc-----------X---|---|---X---|---|-------------release------------- 73 | | | | | | | 74 | t[1]: -----------alloc---------------X---|-------X---|-------------release------------- 75 | | | | | 76 | t[2]: -----------alloc-------------------X-----------X-------------release------------- 77 | | | 78 | t[3]: -----------alloc---------------------------------------------release------------- 79 | """, use_unicode_characters=False) 80 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic_mod/__init__.py: -------------------------------------------------------------------------------- 1 | from .add_mod import ( 2 | do_plus_const_mod, 3 | do_plus_mod, 4 | ) 5 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic_mod/add_mod.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | from quantumpseudocode.ops import semi_quantum 3 | 4 | 5 | def do_plus_const_mod_classical(*, 6 | lvalue: qp.IntBuf, 7 | offset: int, 8 | modulus: int, 9 | forward: bool = True): 10 | if not forward: 11 | offset *= -1 12 | lvalue[:] = (int(lvalue) + offset) % modulus 13 | 14 | 15 | def do_plus_mod_classical(*, 16 | lvalue: qp.IntBuf, 17 | offset: qp.IntBuf, 18 | modulus: int, 19 | forward: bool = True): 20 | offset = int(offset) 21 | if not forward: 22 | offset *= -1 23 | lvalue[:] = (int(lvalue) + offset) % modulus 24 | 25 | 26 | @semi_quantum(alloc_prefix='_do_plus_const_mod_', classical=do_plus_const_mod_classical) 27 | def do_plus_const_mod(*, 28 | control: qp.Qubit.Control = True, 29 | lvalue: qp.Quint, 30 | offset: int, 31 | modulus: int, 32 | forward: bool = True): 33 | assert isinstance(control, qp.QubitIntersection) and len(control.qubits) <= 1 34 | assert isinstance(lvalue, qp.Quint) 35 | assert isinstance(offset, int) 36 | assert isinstance(modulus, int) 37 | assert modulus > 0 38 | n = (modulus - 1).bit_length() 39 | assert len(lvalue) >= n 40 | if not forward: 41 | offset *= -1 42 | offset %= modulus 43 | 44 | if not modulus & (modulus - 1): 45 | lvalue += offset & qp.controlled_by(control) 46 | return 47 | 48 | with qp.qalloc(name='mod_cmp') as q: 49 | q.init(lvalue >= modulus - offset, controls=control) 50 | lvalue += offset & qp.controlled_by(control) 51 | lvalue -= modulus & qp.controlled_by(q & control) 52 | q.clear(lvalue < offset, controls=control) 53 | 54 | 55 | @semi_quantum(alloc_prefix='_do_plus_mod_', classical=do_plus_mod_classical) 56 | def do_plus_mod(control: 'qp.Qubit.Control' = True, 57 | *, 58 | lvalue: qp.Quint, 59 | offset: qp.Quint.Borrowed, 60 | modulus: int, 61 | forward: bool = True): 62 | assert isinstance(lvalue, qp.Quint) 63 | assert modulus > 0 64 | n = (modulus - 1).bit_length() 65 | assert len(offset) <= len(lvalue) 66 | assert len(lvalue) >= n 67 | 68 | if not modulus & (modulus - 1): 69 | if forward: 70 | lvalue[:n] += offset & qp.controlled_by(control) 71 | else: 72 | lvalue[:n] -= offset & qp.controlled_by(control) 73 | return 74 | 75 | with offset.hold_padded_to(n) as offset: 76 | with qp.qalloc(name='mod_cmp') as q: 77 | if forward: 78 | offset ^= -1 79 | offset += modulus + 1 80 | q.init(lvalue >= offset, controls=control) 81 | offset -= modulus + 1 82 | offset ^= -1 83 | 84 | lvalue += offset & qp.controlled_by(control) 85 | lvalue -= modulus & qp.controlled_by(q & control) 86 | q.clear(lvalue < offset, controls=control) 87 | else: 88 | q.init(lvalue < offset, controls=control) 89 | lvalue += modulus & qp.controlled_by(q & control) 90 | lvalue -= offset & qp.controlled_by(control) 91 | 92 | offset ^= -1 93 | offset += modulus + 1 94 | q.clear(lvalue >= offset, controls=control) 95 | offset -= modulus + 1 96 | offset ^= -1 97 | 98 | -------------------------------------------------------------------------------- /quantumpseudocode/arithmetic_mod/add_mod_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | def test_quantum_classical_consistent(): 7 | qp.testing.assert_semi_quantum_func_is_consistent( 8 | qp.arithmetic_mod.do_plus_const_mod, 9 | fuzz_space={ 10 | 'modulus': lambda: random.randint(1, 63), 11 | 'lvalue': lambda modulus: qp.IntBuf.random_mod(modulus), 12 | 'offset': lambda modulus: random.randint(-3 * modulus, 3 * modulus), 13 | 'forward': [False, True], 14 | }, 15 | fuzz_count=100) 16 | 17 | qp.testing.assert_semi_quantum_func_is_consistent( 18 | qp.arithmetic_mod.do_plus_mod, 19 | fuzz_space={ 20 | 'modulus': lambda: random.randint(1, 63), 21 | 'lvalue': lambda modulus: qp.IntBuf.random_mod(modulus), 22 | 'offset': lambda modulus: random.randint(0, modulus - 1), 23 | 'forward': [False, True], 24 | }, 25 | fuzz_count=100) 26 | -------------------------------------------------------------------------------- /quantumpseudocode/buf/__init__.py: -------------------------------------------------------------------------------- 1 | from .int_buffer import ( 2 | Buffer, 3 | IntBuf, 4 | RawConcatBuffer, 5 | RawIntBuffer, 6 | RawWindowBuffer, 7 | ) 8 | -------------------------------------------------------------------------------- /quantumpseudocode/buf/int_buffer.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Iterable, Sequence, Union 3 | 4 | 5 | class Buffer: 6 | """An abstract bit array interface.""" 7 | def __getitem__(self, item) -> int: 8 | raise NotImplementedError() 9 | 10 | def __setitem__(self, key, value): 11 | raise NotImplementedError() 12 | 13 | def __len__(self): 14 | raise NotImplementedError() 15 | 16 | 17 | class RawIntBuffer(Buffer): 18 | """A bit array backed by the bits in a python int.""" 19 | 20 | def __init__(self, val: int, length: int): 21 | assert 0 <= val < 1 << length 22 | self._val = val 23 | self._len = length 24 | 25 | def __getitem__(self, item): 26 | if isinstance(item, int): 27 | assert 0 <= item < self._len 28 | return (self._val >> item) & 1 29 | 30 | if isinstance(item, slice): 31 | assert item.step is None 32 | assert 0 <= item.start <= item.stop <= len(self) 33 | length = item.stop - item.start 34 | return (self._val >> item.start) & ~(-1 << length) 35 | 36 | return NotImplemented 37 | 38 | def __setitem__(self, key, value): 39 | if isinstance(key, int): 40 | assert value in [False, True, 0, 1] 41 | assert 0 <= key < self._len 42 | mask = 1 << key 43 | if value: 44 | self._val |= mask 45 | else: 46 | self._val &= ~mask 47 | return self[key] 48 | 49 | if isinstance(key, slice): 50 | assert key.step is None 51 | assert 0 <= key.start <= key.stop <= self._len 52 | n = key.stop - key.start 53 | assert 0 <= value < 1 << n 54 | mask = ~(-1 << n) 55 | written = value & mask 56 | self._val &= ~(mask << key.start) 57 | self._val |= written << key.start 58 | return written 59 | 60 | return NotImplemented 61 | 62 | def __eq__(self, other): 63 | if isinstance(other, type(self)): 64 | return self._val == other._val and self._len == other._len 65 | return NotImplemented 66 | 67 | def __len__(self): 68 | return self._len 69 | 70 | def __str__(self): 71 | return ''.join(str(self[i]) for i in range(len(self))[::-1]) 72 | 73 | def __repr__(self): 74 | return 'RawIntBuffer(0b{}, {!r})'.format(str(self), self._len) 75 | 76 | 77 | class RawConcatBuffer(Buffer): 78 | """Exposes two buffers as one concatenated buffer.""" 79 | 80 | def __init__(self, buf0: Buffer, buf1: Buffer): 81 | assert buf0 is not buf1 82 | self.buf0 = buf0 83 | self.buf1 = buf1 84 | 85 | @staticmethod 86 | def balanced_concat(parts: Sequence[Buffer]) -> 'Buffer': 87 | """Creates a buffer over many concatenated parts. 88 | 89 | Arranges RawConcatBuffer instances into a balanced search tree. 90 | """ 91 | if len(parts) == 0: 92 | return RawIntBuffer(val=0, length=0) 93 | if len(parts) == 1: 94 | return parts[0] 95 | middle = len(parts) >> 1 96 | return RawConcatBuffer( 97 | RawConcatBuffer.balanced_concat(parts[:middle]), 98 | RawConcatBuffer.balanced_concat(parts[middle:])) 99 | 100 | def __getitem__(self, item): 101 | n = len(self.buf0) 102 | 103 | if isinstance(item, int): 104 | if item < n: 105 | return self.buf0[item] 106 | return self.buf1[item - n] 107 | 108 | if isinstance(item, slice): 109 | assert item.step is None 110 | assert 0 <= item.start <= item.stop <= len(self) 111 | b0 = item.start >= n 112 | b1 = item.stop > n 113 | if not b0 and not b1: 114 | return self.buf0[item] 115 | if b0 and b1: 116 | return self.buf1[item.start - n:item.stop - n] 117 | cut = item.stop - n 118 | k = n - item.start 119 | return self.buf0[item.start:n] | (self.buf1[0:cut] << k) 120 | 121 | return NotImplemented 122 | 123 | def __setitem__(self, key, value): 124 | n = len(self.buf0) 125 | 126 | if isinstance(key, int): 127 | assert value in [False, True, 0, 1] 128 | assert 0 <= key < len(self) 129 | if key < n: 130 | self.buf0[key] = value 131 | else: 132 | self.buf1[key] = value 133 | 134 | if isinstance(key, slice): 135 | assert key.step is None 136 | assert 0 <= key.start <= key.stop <= len(self) 137 | b0 = key.start >= n 138 | b1 = key.stop > n 139 | if not b0 and not b1: 140 | self.buf0[key] = value 141 | return value 142 | if b0 and b1: 143 | self.buf1[key.start - n:key.stop - n] = value 144 | return value 145 | cut = key.stop - n 146 | k = n - key.start 147 | self.buf0[key.start:n] = value & ~(-1 << k) 148 | self.buf1[0:cut] = value >> k 149 | return value 150 | 151 | return NotImplemented 152 | 153 | def __eq__(self, other): 154 | if isinstance(other, type(self)): 155 | return self.buf0 == other.buf0 and self.buf1 == other.buf1 156 | return NotImplemented 157 | 158 | def __len__(self): 159 | return len(self.buf0) + len(self.buf1) 160 | 161 | def __str__(self): 162 | return '{} {}'.format(self.buf0, self.buf1) 163 | 164 | def __repr__(self): 165 | return 'RawConcatBuffer({!r}, {!r})'.format(self.buf0, self.buf1) 166 | 167 | 168 | class RawWindowBuffer(Buffer): 169 | """Exposes a subset of a buffer as a buffer.""" 170 | 171 | def __new__(cls, 172 | buf: Buffer, 173 | start: int, 174 | stop: int): 175 | if isinstance(buf, RawWindowBuffer): 176 | result = RawWindowBuffer( 177 | buf._buf, 178 | buf._start + start, 179 | buf._start + stop) 180 | return result 181 | return super().__new__(cls) 182 | 183 | def __init__(self, 184 | buf: Buffer, 185 | start: int, 186 | stop: int): 187 | if isinstance(buf, RawWindowBuffer): 188 | return 189 | self._buf = buf 190 | self._start = start 191 | self._stop = stop 192 | 193 | def __getitem__(self, item) -> int: 194 | if isinstance(item, int): 195 | index = range(self._start, self._stop)[item] 196 | return self._buf[index] 197 | 198 | if isinstance(item, slice): 199 | assert item.step is None 200 | span = range(self._start, self._stop)[item] 201 | assert span.start <= span.stop 202 | return self._buf[span.start:span.stop] 203 | 204 | return NotImplemented 205 | 206 | def __setitem__(self, key, value): 207 | if isinstance(key, int): 208 | index = range(self._start, self._stop)[key] 209 | self._buf[index] = value 210 | assert self._buf[index] == value 211 | return value 212 | 213 | if isinstance(key, slice): 214 | assert key.step is None 215 | assert 0 <= key.start <= key.stop <= len(self) 216 | n = key.stop - key.start 217 | assert 0 <= value < 1 << n 218 | span = range(self._start, self._stop)[key] 219 | self._buf[span.start:span.stop] = value 220 | return value 221 | 222 | return NotImplemented 223 | 224 | def __eq__(self, other): 225 | if isinstance(other, type(self)): 226 | return self._buf == other._buf and self._start == other._start and self._stop == other._stop 227 | return NotImplemented 228 | 229 | def __len__(self): 230 | return self._stop - self._start 231 | 232 | def __str__(self): 233 | return '{}[{}:{}]'.format(self._buf, self._start, self._stop) 234 | 235 | def __repr__(self): 236 | return 'RawWindowBuffer({!r}, {!r}, {!r})'.format(self._buf, self._start, self._stop) 237 | 238 | 239 | class IntBuf: 240 | """A fixed-width unsigned integer backed by a mutable bit buffer. 241 | 242 | Basically a complicated pointer into allocated memory. 243 | """ 244 | 245 | def __init__(self, buffer: Buffer): 246 | self._buf = buffer 247 | assert isinstance(buffer, Buffer), 'Not a Buffer: {!r}'.format(buffer) 248 | 249 | def signed_int(self) -> int: 250 | """Return value as a signed int instead of an unsigned int.""" 251 | if not len(self): 252 | return 0 253 | result = int(self) 254 | if self[len(self) - 1]: 255 | result -= 1 << len(self) 256 | return result 257 | 258 | @staticmethod 259 | def random(length: Union[int, range, Iterable[int]]) -> 'IntBuf': 260 | """Generates an IntBuf with random contents and a length sampled from the given allowed value(s).""" 261 | length = length if isinstance(length, int) else random.choice(length) 262 | return IntBuf.raw(length=length, val=random.randint(0, 2**length-1)) 263 | 264 | @staticmethod 265 | def random_mod(modulus: int) -> 'IntBuf': 266 | """Generates a minimum length IntBuf for the given modulus, with a random value less than the modulus.""" 267 | length = (modulus - 1).bit_length() 268 | return IntBuf.raw(length=length, val=random.randint(0, modulus - 1)) 269 | 270 | def copy(self) -> 'IntBuf': 271 | return IntBuf.raw(length=len(self), val=int(self)) 272 | 273 | def __len__(self): 274 | """The number of bits in this fixed-width integer.""" 275 | return len(self._buf) 276 | 277 | def __int__(self): 278 | """The unsigned value of this fixed-width integer.""" 279 | return self._buf[0:len(self._buf)] 280 | 281 | def padded(self, pad_len: int) -> 'IntBuf': 282 | """Returns a variant of this IntBuf with an extended width. 283 | 284 | The extra bits go at the end (the most significant side). The receiving 285 | IntBuf is not modified, but the head of the returned IntBuf is pointed 286 | at the same memory so modifying one's value will modify the other's. 287 | """ 288 | if pad_len == 0: 289 | return self 290 | return IntBuf(RawConcatBuffer(self._buf, RawIntBuffer(0, pad_len))) 291 | 292 | @classmethod 293 | def zero(cls, length: int) -> 'IntBuf': 294 | """Returns a fresh zero'd IntBuf with the given length.""" 295 | return IntBuf(RawIntBuffer(0, length)) 296 | 297 | @classmethod 298 | def raw(cls, *, val: int, length: int) -> 'IntBuf': 299 | """Returns a fresh IntBuf with the given length and starting value.""" 300 | return IntBuf(RawIntBuffer(val, length)) 301 | 302 | @classmethod 303 | def concat(cls, bufs: Iterable['IntBuf']) -> 'IntBuf': 304 | """An IntBuf backed by the concatenated buffers of the given IntBufs.""" 305 | frozen = list(bufs) 306 | if not frozen: 307 | return IntBuf.zero(0) 308 | seed = frozen[0] 309 | for buf in frozen[1:]: 310 | seed = seed.then(buf) 311 | return seed 312 | 313 | def then(self, other: 'IntBuf') -> 'IntBuf': 314 | """An IntBuf backed by the concatenated buffers of the given IntBufs.""" 315 | return IntBuf(RawConcatBuffer(self._buf, other._buf)) 316 | 317 | def __getitem__(self, item): 318 | """Get a bit or mutable window into this IntBuf.""" 319 | 320 | if isinstance(item, int): 321 | index = range(0, len(self._buf))[item] 322 | return self._buf[index] 323 | 324 | if isinstance(item, slice): 325 | assert item.step is None 326 | span = range(0, len(self._buf))[item] 327 | assert span.start <= span.stop 328 | return IntBuf(RawWindowBuffer(self._buf, span.start, span.stop)) 329 | 330 | return NotImplemented 331 | 332 | def __setitem__(self, key, value): 333 | """Overwrite a bit or window in this IntBuf.""" 334 | 335 | if isinstance(key, int): 336 | index = range(0, len(self._buf))[key] 337 | self._buf[index] = value 338 | assert self._buf[index] == value 339 | return value 340 | 341 | if isinstance(key, slice): 342 | assert key.step is None 343 | span = range(0, len(self._buf))[key] 344 | written = int(value) & ~(-1 << (span.stop - span.start)) 345 | self._buf[span.start:span.stop] = written 346 | assert self._buf[span.start:span.stop] == written 347 | return written 348 | 349 | return NotImplemented 350 | 351 | def __eq__(self, other): 352 | if isinstance(other, (int, IntBuf)): 353 | return int(self) == int(other) 354 | return NotImplemented 355 | 356 | def __str__(self): 357 | return ''.join(str(self[i]) for i in range(len(self))) 358 | 359 | def __bool__(self): 360 | return bool(int(self)) 361 | 362 | def __iadd__(self, other): 363 | if isinstance(other, (int, IntBuf)): 364 | self[:] = int(self) + int(other) 365 | return self 366 | return NotImplemented 367 | 368 | def __imul__(self, other): 369 | if isinstance(other, (int, IntBuf)): 370 | self[:] = int(self) * int(other) 371 | return self 372 | return NotImplemented 373 | 374 | def __isub__(self, other): 375 | if isinstance(other, (int, IntBuf)): 376 | self[:] = int(self) - int(other) 377 | return self 378 | return NotImplemented 379 | 380 | def __ixor__(self, other): 381 | if isinstance(other, (int, IntBuf)): 382 | self[:] = int(self) ^ int(other) 383 | return self 384 | return NotImplemented 385 | 386 | def __iand__(self, other): 387 | if isinstance(other, (int, IntBuf)): 388 | self[:] = int(self) & int(other) 389 | return self 390 | return NotImplemented 391 | 392 | def __ior__(self, other): 393 | if isinstance(other, (int, IntBuf)): 394 | self[:] = int(self) | int(other) 395 | return self 396 | return NotImplemented 397 | 398 | def __repr__(self): 399 | return 'IntBuf({!r})'.format(self._buf) 400 | -------------------------------------------------------------------------------- /quantumpseudocode/buf/int_buffer_test.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def test_int_buffer(): 5 | e = qp.IntBuf.zero(10) 6 | assert not bool(e) 7 | assert int(e) == 0 8 | assert e[0] == 0 9 | assert e[1] == 0 10 | 11 | e += 2 12 | assert int(e) == 2 13 | assert bool(e) 14 | assert e[0] == 0 15 | assert e[1] == 1 16 | 17 | assert str(e) == '0100000000' 18 | assert str(e[1:5]) == '1000' 19 | 20 | e -= 43 21 | assert int(e) == 983 22 | e[2:] += 1 23 | assert int(e) == 987 24 | 25 | 26 | def test_ref_iadd(): 27 | e = qp.IntBuf.zero(10) 28 | e += 33 29 | assert int(e) == 33 30 | f = e[2:] 31 | f -= 1 32 | assert int(e) == 29 33 | f += 1 34 | assert int(e) == 33 35 | 36 | 37 | def test_concat(): 38 | a = qp.RawIntBuffer(0, 5) 39 | b = qp.RawIntBuffer(0, 5) 40 | c = qp.RawConcatBuffer(a, b) 41 | e = qp.IntBuf(c) 42 | 43 | e += 33 44 | assert int(e) == 33 45 | f = e[2:] 46 | f -= 1 47 | assert int(e) == 29 48 | f += 1 49 | assert int(e) == 33 50 | 51 | e[:] = 0b1001100101 52 | assert e[:5] == 0b00101 53 | assert e[5:] == 0b10011 54 | assert e[3:8] == 0b01100 55 | 56 | e[:] = 0 57 | e[:5] = 0b11111 58 | assert int(e) == 0b0000011111 59 | e[:] = 0 60 | e[5:] = 0b11111 61 | assert int(e) == 0b1111100000 62 | e[:] = 0 63 | e[3:8] = 0b11111 64 | assert int(e) == 0b0011111000 65 | 66 | 67 | def test_raw_concat_buffer_many(): 68 | a = qp.RawIntBuffer(0, 5) 69 | b = qp.RawIntBuffer(0, 5) 70 | c = qp.RawIntBuffer(0, 5) 71 | d = qp.RawIntBuffer(0, 5) 72 | e = qp.RawIntBuffer(0, 5) 73 | assert qp.RawConcatBuffer.balanced_concat([a]) is a 74 | assert qp.RawConcatBuffer.balanced_concat([a, b]) == qp.RawConcatBuffer( 75 | a, b) 76 | assert qp.RawConcatBuffer.balanced_concat([a, b, c]) == qp.RawConcatBuffer( 77 | a, qp.RawConcatBuffer(b, c)) 78 | assert qp.RawConcatBuffer.balanced_concat( 79 | [a, b, c, d]) == qp.RawConcatBuffer( 80 | qp.RawConcatBuffer(a, b), 81 | qp.RawConcatBuffer(c, d)) 82 | assert qp.RawConcatBuffer.balanced_concat( 83 | [a, b, c, d, e]) == qp.RawConcatBuffer( 84 | qp.RawConcatBuffer(a, b), 85 | qp.RawConcatBuffer(c, qp.RawConcatBuffer(d, e))) 86 | 87 | 88 | def test_window_flatten(): 89 | a = qp.RawIntBuffer(0b101101, 6) 90 | b = qp.RawWindowBuffer(a, 1, 5) 91 | c = qp.RawWindowBuffer(b, 1, 3) 92 | assert repr(c) == 'RawWindowBuffer(RawIntBuffer(0b101101, 6), 2, 4)' 93 | -------------------------------------------------------------------------------- /quantumpseudocode/control.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Any, Optional, Tuple 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | @cirq.value_equality 9 | class ControlledRValue(qp.RValue): 10 | def __init__(self, 11 | controls: 'qp.QubitIntersection', 12 | rvalue: 'qp.RValue'): 13 | assert isinstance(controls, qp.QubitIntersection) 14 | self.controls = controls 15 | self.rvalue = rvalue 16 | 17 | @staticmethod 18 | def split(val: Any) -> Tuple[Any, 'qp.QubitIntersection']: 19 | controls = qp.QubitIntersection.ALWAYS 20 | if isinstance(val, qp.ControlledRValue): 21 | val, controls = val.rvalue, val.controls 22 | if isinstance(val, qp.RValue): 23 | val = val.trivial_unwrap() 24 | return val, controls 25 | 26 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 27 | if not self.controls.resolve(sim_state, False): 28 | return 0 29 | return sim_state.resolve_location(self.rvalue, False) 30 | 31 | def alloc_storage_location(self, name: Optional[str] = None): 32 | return self.rvalue.alloc_storage_location(name=name) 33 | 34 | def init_storage_location(self, 35 | location: Any, 36 | controls: 'qp.QubitIntersection'): 37 | self.rvalue.init_storage_location(location, controls & self.controls) 38 | 39 | def clear_storage_location(self, 40 | location: Any, 41 | controls: 'qp.QubitIntersection'): 42 | self.rvalue.clear_storage_location(location, controls & self.controls) 43 | 44 | def __str__(self): 45 | return 'controlled({}, {})'.format(self.rvalue, self.controls) 46 | 47 | def _value_equality_values_(self): 48 | return self.rvalue, self.controls 49 | 50 | def __repr__(self): 51 | return 'qp.ControlledRValue({!r}, {!r})'.format(self.controls, 52 | self.rvalue) 53 | 54 | 55 | @cirq.value_equality 56 | class ControlledLValue: 57 | def __init__(self, 58 | controls: 'qp.QubitIntersection', 59 | lvalue: Union['qp.Qubit', 60 | 'qp.Quint', 61 | 'qp.Qureg', 62 | 'qp.ControlledLValue', 63 | 'qp.QuintMod']): 64 | assert isinstance(controls, qp.QubitIntersection) 65 | if isinstance(lvalue, ControlledLValue): 66 | lvalue = lvalue.lvalue 67 | controls = controls & lvalue.controls 68 | self.lvalue = lvalue 69 | self.controls = controls 70 | 71 | @staticmethod 72 | def split(val: Any) -> Tuple[Any, 'qp.QubitIntersection']: 73 | if isinstance(val, qp.ControlledLValue): 74 | return val.lvalue, val.controls 75 | return val, qp.QubitIntersection.ALWAYS 76 | 77 | def _value_equality_values_(self): 78 | return self.lvalue, self.controls 79 | 80 | def __repr__(self): 81 | return 'qp.ControlledLValue({!r}, {!r})'.format(self.controls, self.lvalue) 82 | 83 | 84 | class ControlledBy: 85 | def __init__(self, controls: qp.QubitIntersection): 86 | self.controls = controls 87 | 88 | def __and__(self, other): 89 | if isinstance(other, ControlledRValue): 90 | return ControlledRValue(self.controls & other.controls, 91 | other.rvalue) 92 | if isinstance(other, (int, qp.Quint, qp.RValue)): 93 | return ControlledRValue(self.controls, qp.rval(other)) 94 | return NotImplemented 95 | 96 | def __rand__(self, other): 97 | return self.__and__(other) 98 | 99 | 100 | def controlled_by(qubits: Union[bool, qp.Qubit, qp.QubitIntersection]): 101 | if isinstance(qubits, qp.Qubit): 102 | qubits = qp.QubitIntersection((qubits,)) 103 | if qubits is False: 104 | qubits = qp.QubitIntersection.NEVER 105 | if qubits is True: 106 | qubits = qp.QubitIntersection.ALWAYS 107 | return ControlledBy(qubits) 108 | -------------------------------------------------------------------------------- /quantumpseudocode/control_test.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def test_controls_xor(): 5 | def f(c: qp.Qubit, t: qp.Quint): 6 | t ^= 1 & qp.controlled_by(c) 7 | 8 | r = qp.testing.sim_call(f, True, 2) 9 | assert r == qp.ArgsAndKwargs([True, 3], {}) 10 | 11 | r = qp.testing.sim_call(f, False, 2) 12 | assert r == qp.ArgsAndKwargs([False, 2], {}) 13 | -------------------------------------------------------------------------------- /quantumpseudocode/log_cirq.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import random 3 | from typing import List, Union, Callable, Any, Optional, Tuple 4 | 5 | import cirq 6 | import quantumpseudocode as qp 7 | 8 | 9 | class MultiNot(cirq.Operation): 10 | def __init__(self, qubits): 11 | self._qubits = tuple(qubits) 12 | 13 | def with_qubits(self, *new_qubits): 14 | return MultiNot(new_qubits) 15 | 16 | @property 17 | def qubits(self): 18 | return self._qubits 19 | 20 | def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs): 21 | return ['X'] * args.known_qubit_count 22 | 23 | def __repr__(self): 24 | return f'qp.MultiNot({self._qubits!r})' 25 | 26 | 27 | class MeasureResetGate(cirq.SingleQubitGate): 28 | def _circuit_diagram_info_(self, args): 29 | return 'Mr' 30 | 31 | 32 | class CirqLabelOp(cirq.Operation): 33 | def __init__(self, qubits, label: str): 34 | self._qubits = tuple(qubits) 35 | self.label = label 36 | 37 | @property 38 | def qubits(self) -> Tuple['cirq.Qid', ...]: 39 | return self._qubits 40 | 41 | def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'cirq.Operation': 42 | raise NotImplementedError() 43 | 44 | def _circuit_diagram_info_(self, args): 45 | return (self.label,) * len(self.qubits) 46 | 47 | def __repr__(self): 48 | return f'CirqLabelOp({self.qubits!r}, {self.label!r})' 49 | 50 | 51 | class LogCirqCircuit(qp.Sink): 52 | def __init__(self): 53 | super().__init__() 54 | self.circuit = cirq.Circuit() 55 | 56 | def _val(self): 57 | return self.circuit 58 | 59 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 60 | targets = [cirq.NamedQubit(str(q)) for q in qureg] 61 | if targets: 62 | self.circuit.append(CirqLabelOp(targets, 'alloc'), cirq.InsertStrategy.NEW_THEN_INLINE) 63 | 64 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 65 | targets = [cirq.NamedQubit(str(q)) for q in op.qureg] 66 | if targets: 67 | self.circuit.append(CirqLabelOp(targets, 'release'), cirq.InsertStrategy.NEW_THEN_INLINE) 68 | 69 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 70 | if controls.bit: 71 | if len(controls.qubits): 72 | g = cirq.Z 73 | for _ in range(len(controls.qubits) - 1): 74 | g = cirq.ControlledGate(g) 75 | ctrls = [cirq.NamedQubit(str(q)) for q in controls.qubits] 76 | self.circuit.append(g(*ctrls), 77 | cirq.InsertStrategy.NEW_THEN_INLINE) 78 | else: 79 | self.circuit.append(cirq.GlobalPhaseOperation(-1), cirq.InsertStrategy.NEW_THEN_INLINE) 80 | 81 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 82 | ctrls = [cirq.NamedQubit(str(q)) for q in controls.qubits] 83 | if targets and controls.bit: 84 | targs = [cirq.NamedQubit(str(q)) for q in targets] 85 | self.circuit.append(MultiNot(targs).controlled_by(*ctrls), 86 | cirq.InsertStrategy.NEW_THEN_INLINE) 87 | 88 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 89 | raise NotImplementedError() 90 | 91 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 92 | qubits = [cirq.NamedQubit(str(q)) for q in qureg] 93 | if not qubits: 94 | return 95 | if reset: 96 | self.circuit.append(MeasureResetGate().on_each(*qubits), 97 | cirq.InsertStrategy.NEW_THEN_INLINE) 98 | else: 99 | self.circuit.append(cirq.measure(*qubits), 100 | cirq.InsertStrategy.NEW_THEN_INLINE) 101 | 102 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 103 | raise NotImplementedError() 104 | 105 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 106 | qubits = [cirq.NamedQubit(str(q)) for q in qureg] 107 | if not qubits: 108 | return 109 | self.circuit.append(CirqLabelOp(qubits, 'Mxc'), cirq.InsertStrategy.NEW_THEN_INLINE) 110 | 111 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 112 | targets = [cirq.NamedQubit(str(q)) for q in qureg] 113 | if not targets: 114 | return 115 | self.circuit.append(CirqLabelOp(targets, 'cxM'), cirq.InsertStrategy.NEW_THEN_INLINE) 116 | 117 | 118 | class CountNots(qp.Sink): 119 | def __init__(self): 120 | super().__init__() 121 | self.counts = collections.Counter() 122 | 123 | def _val(self): 124 | return self.counts 125 | 126 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 127 | pass 128 | 129 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 130 | pass 131 | 132 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 133 | if controls.bit: 134 | if len(controls.qubits) > 0: 135 | self.counts[len(controls.qubits) - 1] += 1 136 | 137 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 138 | if controls.bit: 139 | if len(controls.qubits) > 1: 140 | self.counts[1] += 2 * (len(targets) - 1) 141 | self.counts[len(controls.qubits)] += 1 142 | else: 143 | self.counts[len(controls.qubits)] += len(targets) 144 | 145 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 146 | raise NotImplementedError() 147 | 148 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 149 | pass 150 | 151 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 152 | raise NotImplementedError() 153 | 154 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', 155 | result: 'qp.StartMeasurementBasedUncomputationResult'): 156 | pass 157 | 158 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 159 | pass 160 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/__init__.py: -------------------------------------------------------------------------------- 1 | from .qubit import ( 2 | Qubit, 3 | ) 4 | 5 | from .qureg import ( 6 | NamedQureg, 7 | Qureg, 8 | RangeQureg, 9 | RawQureg, 10 | ) 11 | 12 | from .quint import ( 13 | Quint, 14 | ) 15 | 16 | from .quint_mod import ( 17 | QuintMod, 18 | ) 19 | 20 | from .padded import ( 21 | pad, 22 | pad_all, 23 | PaddedQureg, 24 | ) 25 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/padded.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Callable, Any, Optional 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | def pad(base: 'Union[qp.Qureg, qp.Quint]', *, min_len: int) -> 'qp.PaddedQureg': 7 | assert min_len >= 0 8 | if isinstance(base, qp.Quint): 9 | return PaddedQureg(base.qureg, min_len, qp.Quint) 10 | else: 11 | return PaddedQureg(base, min_len) 12 | 13 | 14 | def pad_all(*bases: 'Union[qp.Qureg, qp.Quint]', 15 | min_len: int) -> 'qp.MultiWith': 16 | assert min_len >= 0 17 | return qp.MultiWith(pad(b, min_len=min_len) for b in bases) 18 | 19 | 20 | class PaddedQureg: 21 | def __init__(self, 22 | base: 'qp.Qureg', 23 | min_len: int, 24 | wrapper: Callable[[Any], Any] = lambda e: e): 25 | self.base = base 26 | self.padded = None 27 | self.min_len = min_len 28 | self.wrapper = wrapper 29 | 30 | def __len__(self): 31 | return len(self.padded) 32 | 33 | def __getitem__(self, item): 34 | return self.padded[item] 35 | 36 | def __enter__(self) -> 'qp.Qureg': 37 | if len(self.base) >= self.min_len: 38 | return self.wrapper(self.base) 39 | 40 | assert self.padded is None 41 | sub_name = str(self.base) if isinstance(self.base, qp.NamedQureg) else '' 42 | self.padded = qp.qalloc( 43 | name='{}_pad'.format(sub_name), 44 | len=self.min_len - len(self.base) 45 | ) 46 | return self.wrapper(qp.RawQureg(list(self.base) + list(self.padded))) 47 | 48 | def __exit__(self, exc_type, exc_val, exc_tb): 49 | if len(self.base) >= self.min_len: 50 | return 51 | 52 | if exc_type is None: 53 | assert self.padded is not None 54 | qp.qfree(self.padded) 55 | self.padded = None 56 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/qubit.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple, Iterable, Union 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from quantumpseudocode import sink 7 | 8 | 9 | @cirq.value_equality 10 | class Qubit: 11 | Borrowed = Union[int, 'qp.Qubit', 'qp.RValue[bool]'] 12 | Control = Union[None, 'qp.QubitIntersection', 'qp.Qubit', bool, 'qp.RValue[bool]'] 13 | 14 | def __init__(self, 15 | name: str = '', 16 | index: Optional[int] = None): 17 | self.name = name 18 | self.index = index 19 | 20 | @property 21 | def qureg(self): 22 | if self.index is None: 23 | return qp.NamedQureg(self.name, length=1) 24 | return qp.RawQureg([qp.Qubit(self.name, self.index)]) 25 | 26 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 27 | buf = sim_state.quint_buf(qp.Quint(self.qureg)) 28 | return buf if allow_mutate else bool(int(buf)) 29 | 30 | def _value_equality_values_(self): 31 | return self.name, self.index 32 | 33 | def __str__(self): 34 | if self.index is None: 35 | return str(self.name) 36 | return '{}[{}]'.format(self.name, self.index) 37 | 38 | def __repr__(self): 39 | return 'qp.Qubit({!r}, {!r})'.format(self.name, self.index) 40 | 41 | def __and__(self, other): 42 | if isinstance(other, Qubit): 43 | return qp.QubitIntersection((self, other)) 44 | if other in [False, 0]: 45 | return qp.QubitIntersection.NEVER 46 | if other in [True, 1]: 47 | return self 48 | return NotImplemented 49 | 50 | def __rand__(self, other): 51 | return self.__and__(other) 52 | 53 | def init(self, 54 | value: Union[bool, 'qp.RValue[bool]'], 55 | controls: 'qp.QubitIntersection' = None): 56 | if controls is None: 57 | controls = qp.QubitIntersection.ALWAYS 58 | qp.rval(value).init_storage_location(self, controls) 59 | 60 | def clear(self, 61 | value: Union[bool, 'qp.RValue[bool]'], 62 | controls: 'qp.QubitIntersection' = None): 63 | if controls is None: 64 | controls = qp.QubitIntersection.ALWAYS 65 | qp.rval(value).clear_storage_location(self, controls) 66 | 67 | def __ixor__(self, other): 68 | other, controls = qp.ControlledRValue.split(other) 69 | if controls == qp.QubitIntersection.NEVER: 70 | return self 71 | 72 | other_as_control = qp.QubitIntersection.try_from(other) 73 | if other_as_control is not None: 74 | sink.global_sink.do_toggle(self.qureg, other_as_control & controls) 75 | return self 76 | 77 | rev = getattr(other, '__rixor__', None) 78 | if rev is not None: 79 | return rev(qp.ControlledLValue(controls, self)) 80 | 81 | return NotImplemented 82 | 83 | def __enter__(self): 84 | return self 85 | 86 | def __exit__(self, exc_type, exc_val, exc_tb): 87 | qp.qfree(self) 88 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/qubit_test.py: -------------------------------------------------------------------------------- 1 | import cirq 2 | import pytest 3 | 4 | import quantumpseudocode as qp 5 | 6 | 7 | def test_init(): 8 | eq = cirq.testing.EqualsTester() 9 | 10 | q1 = qp.Qubit('test', 10) 11 | q2 = qp.Qubit('test', 10) 12 | assert str(q1) == str(q2) == 'test[10]' 13 | 14 | eq.add_equality_group(qp.Qubit(), qp.Qubit()) 15 | eq.add_equality_group(q1, q2) 16 | eq.add_equality_group(qp.Qubit('q')) 17 | 18 | 19 | def test_and(): 20 | a = qp.Qubit('a') 21 | b = qp.Qubit('b') 22 | c = qp.Qubit('c') 23 | d = qp.Qubit('d') 24 | s = qp.QubitIntersection((c, d)) 25 | assert a & b == qp.QubitIntersection((a, b)) 26 | assert a & b & c == qp.QubitIntersection((a, b, c)) 27 | assert a & s == qp.QubitIntersection((a, c, d)) 28 | assert a & False == qp.QubitIntersection.NEVER 29 | assert a & True is a 30 | assert False & a == qp.QubitIntersection.NEVER 31 | assert True & a is a 32 | 33 | 34 | def test_ixor(): 35 | q = qp.Qubit('q') 36 | c = qp.Qubit('c') 37 | d = qp.Qubit('d') 38 | 39 | # Unsupported classes cause type error. 40 | with pytest.raises(TypeError): 41 | q ^= None 42 | class C: 43 | pass 44 | with pytest.raises(TypeError): 45 | q ^= C() 46 | 47 | # False does nothing. True causes toggle. 48 | with qp.LogCirqCircuit() as circuit: 49 | q ^= False 50 | assert len(circuit) == 0 51 | with qp.LogCirqCircuit() as circuit: 52 | q ^= True 53 | cirq.testing.assert_has_diagram(circuit, """ 54 | q: ---X--- 55 | """, use_unicode_characters=False) 56 | 57 | # Qubit and qubit intersection cause controlled toggle. 58 | with qp.LogCirqCircuit() as circuit: 59 | q ^= c 60 | cirq.testing.assert_has_diagram(circuit, """ 61 | c: ---@--- 62 | | 63 | q: ---X--- 64 | """, use_unicode_characters=False) 65 | with qp.LogCirqCircuit() as circuit: 66 | q ^= c & d 67 | cirq.testing.assert_has_diagram(circuit, """ 68 | c: ---@--- 69 | | 70 | d: ---@--- 71 | | 72 | q: ---X--- 73 | """, use_unicode_characters=False) 74 | 75 | # Classes can specify custom behavior via __rixor__. 76 | class Rixor: 77 | def __rixor__(self, other): 78 | qp.phase_flip() 79 | return other 80 | with qp.capture() as out: 81 | q ^= Rixor() 82 | assert out == [('phase_flip', qp.QubitIntersection.ALWAYS)] 83 | 84 | 85 | def test_repr(): 86 | cirq.testing.assert_equivalent_repr( 87 | qp.Qubit('test'), 88 | setup_code='import quantumpseudocode as qp') 89 | 90 | cirq.testing.assert_equivalent_repr( 91 | qp.Qubit('test', 10), 92 | setup_code='import quantumpseudocode as qp') 93 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/quint.py: -------------------------------------------------------------------------------- 1 | from typing import Union, ContextManager 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | class Quint: 7 | Borrowed = Union[int, 'qp.Quint', 'qp.RValue[int]'] 8 | 9 | def __init__(self, qureg: 'qp.Qureg'): 10 | self.qureg = qureg 11 | 12 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 13 | buf = sim_state.quint_buf(self) 14 | return buf if allow_mutate else int(buf) 15 | 16 | def hold_padded_to(self, min_len: int) -> ContextManager['qp.Quint']: 17 | return qp.pad(self, min_len=min_len) 18 | 19 | def __len__(self): 20 | return len(self.qureg) 21 | 22 | def __getitem__(self, item): 23 | if isinstance(item, int): 24 | return self.qureg[item] 25 | return Quint(self.qureg[item]) 26 | 27 | def __rlshift__(self, other): 28 | if other == 1: 29 | return qp.UnaryRValue(self) 30 | return NotImplemented 31 | 32 | def init(self, 33 | value: Union[int, 'qp.RValue[int]'], 34 | controls: 'qp.QubitIntersection' = None): 35 | if controls is None: 36 | controls = qp.QubitIntersection.ALWAYS 37 | qp.rval(value).init_storage_location(self, controls) 38 | 39 | def clear(self, 40 | value: Union[int, 'qp.RValue[int]'], 41 | controls: 'qp.QubitIntersection' = None): 42 | if controls is None: 43 | controls = qp.QubitIntersection.ALWAYS 44 | qp.rval(value).clear_storage_location(self, controls) 45 | 46 | def __setitem__(self, key, value): 47 | if value.qureg != self[key].qureg: 48 | raise NotImplementedError( 49 | "quint.__setitem__ is only for syntax like q[0] ^= q[1]. " 50 | "Don't know how to write {!r} into quint {!r}.".format( 51 | value, key)) 52 | return value 53 | 54 | def __mul__(self, other): 55 | if isinstance(other, int): 56 | return qp.ScaledIntRValue(self, other) 57 | return NotImplemented 58 | 59 | def __rmul__(self, other): 60 | return self.__mul__(other) 61 | 62 | def __le__(self, other): 63 | if isinstance(other, int): 64 | if other < 0: 65 | return qp.rval(False) 66 | if other == 0: 67 | return self == 0 68 | return qp.IfLessThanRVal(qp.rval(self), 69 | qp.rval(other), 70 | qp.rval(True)) 71 | 72 | def __lt__(self, other): 73 | if isinstance(other, int): 74 | if other <= 0: 75 | return qp.rval(False) 76 | return qp.IfLessThanRVal(qp.rval(self), 77 | qp.rval(other), 78 | qp.rval(False)) 79 | 80 | def __eq__(self, other) -> 'qp.RValue[bool]': 81 | if isinstance(other, int): 82 | if other < 0: 83 | return qp.rval(False) 84 | return qp.QuintEqConstRVal(self, other) 85 | return NotImplemented 86 | 87 | def __ne__(self, other) -> 'qp.RValue[bool]': 88 | if isinstance(other, int): 89 | if other < 0: 90 | return qp.rval(True) 91 | return qp.QuintEqConstRVal(self, other, invert=True) 92 | return NotImplemented 93 | 94 | def __ge__(self, other): 95 | if isinstance(other, int): 96 | if other <= 0: 97 | return qp.rval(True) 98 | return qp.IfLessThanRVal(qp.rval(other), 99 | qp.rval(self), 100 | qp.rval(True)) 101 | 102 | def __gt__(self, other): 103 | if isinstance(other, int): 104 | if other < 0: 105 | return qp.rval(True) 106 | if other == 0: 107 | return self != 0 108 | return qp.IfLessThanRVal(qp.rval(other), 109 | qp.rval(self), 110 | qp.rval(False)) 111 | 112 | def __ixor__(self, other): 113 | other, controls = qp.ControlledRValue.split(other) 114 | if controls == qp.QubitIntersection.NEVER: 115 | return self 116 | 117 | if isinstance(other, int): 118 | qp.arithmetic.do_xor_const(lvalue=self, mask=other, control=controls) 119 | return self 120 | 121 | if isinstance(other, Quint): 122 | qp.arithmetic.do_xor(lvalue=self, mask=other, control=controls) 123 | return self 124 | 125 | rev = getattr(other, '__rixor__', None) 126 | if rev is not None: 127 | return rev(qp.ControlledLValue(controls, self)) 128 | 129 | return NotImplemented 130 | 131 | def __iadd__(self, other): 132 | other, controls = qp.ControlledRValue.split(other) 133 | if controls == qp.QubitIntersection.NEVER: 134 | return self 135 | 136 | other_rval = qp.rval(other, default=other) 137 | rev = getattr(other_rval, '__riadd__', None) 138 | if rev is not None: 139 | result = rev(qp.ControlledLValue(controls, self)) 140 | if result is not NotImplemented: 141 | return result 142 | 143 | if isinstance(other, (qp.Quint, qp.RValue)): 144 | qp.arithmetic.do_addition( 145 | lvalue=self, 146 | offset=other, 147 | carry_in=False, 148 | control=controls) 149 | return self 150 | 151 | return NotImplemented 152 | 153 | def __imul__(self, other): 154 | rev = getattr(other, '__rimul__', None) 155 | if rev is not None: 156 | result = rev(self) 157 | if result is not NotImplemented: 158 | return result 159 | 160 | rval_other = qp.rval(other, None) 161 | rev = getattr(rval_other, '__rimul__', None) 162 | if rev is not None: 163 | result = rev(self) 164 | if result is not NotImplemented: 165 | return result 166 | 167 | return NotImplemented 168 | 169 | def __isub__(self, other): 170 | lvalue = self 171 | lvalue ^= -1 172 | lvalue += other 173 | lvalue ^= -1 174 | return lvalue 175 | 176 | def __str__(self): 177 | return str(self.qureg) 178 | 179 | def __repr__(self): 180 | return 'qp.Quint({!r})'.format(self.qureg) 181 | 182 | def __enter__(self): 183 | return self 184 | 185 | def __exit__(self, exc_type, exc_val, exc_tb): 186 | qp.qfree(self) 187 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/quint_mod.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | @cirq.value_equality 9 | class QuintMod: 10 | def __init__(self, qureg: 'qp.Qureg', modulus: int): 11 | assert len(qureg) == qp.ceil_lg2(modulus) 12 | self.qureg = qureg 13 | self.modulus = modulus 14 | 15 | def _value_equality_values_(self): 16 | return self.qureg 17 | 18 | def __len__(self): 19 | return len(self.qureg) 20 | 21 | def __getitem__(self, item): 22 | if isinstance(item, int): 23 | return self.qureg[item] 24 | return qp.Quint(self.qureg[item]) 25 | 26 | def init(self, 27 | value: Union[int, 'qp.RValue[int]'], 28 | controls: 'qp.QubitIntersection' = None): 29 | if controls is None: 30 | controls = qp.QubitIntersection.ALWAYS 31 | qp.rval(value).init_storage_location(self[:], controls) 32 | 33 | def clear(self, 34 | value: 'qp.RValue[int]', 35 | controls: 'qp.QubitIntersection' = None): 36 | if controls is None: 37 | controls = qp.QubitIntersection.ALWAYS 38 | value.clear_storage_location(self[:], controls) 39 | 40 | def __setitem__(self, key, value): 41 | if value != self[key]: 42 | raise NotImplementedError( 43 | "quint.__setitem__ is only for syntax like q[0] += 5. " 44 | "Don't know how to write {!r} into quint {!r}.".format( 45 | value, key)) 46 | return value 47 | 48 | def __iadd__(self, other): 49 | other, controls = qp.ControlledRValue.split(other) 50 | if controls == qp.QubitIntersection.NEVER: 51 | return self 52 | 53 | rval_other = qp.rval(other, default=other) 54 | rev = getattr(rval_other, '__riadd__', None) 55 | if rev is not None: 56 | result = rev(qp.ControlledLValue(controls, self)) 57 | if result is not NotImplemented: 58 | return result 59 | 60 | if isinstance(other, int): 61 | qp.arithmetic_mod.do_plus_const_mod( 62 | lvalue=self[:], 63 | offset=other, 64 | modulus=self.modulus, 65 | control=controls) 66 | return self 67 | 68 | if isinstance(other, (qp.Quint, qp.RValue)): 69 | qp.arithmetic_mod.do_plus_mod( 70 | lvalue=self[:], 71 | offset=other, 72 | modulus=self.modulus, 73 | control=controls) 74 | return self 75 | 76 | return NotImplemented 77 | 78 | def __isub__(self, other): 79 | other, controls = qp.ControlledRValue.split(other) 80 | if controls == qp.QubitIntersection.NEVER: 81 | return self 82 | 83 | rval_other = qp.rval(other, default=other) 84 | rev = getattr(rval_other, '__risub__', None) 85 | if rev is not None: 86 | result = rev(qp.ControlledLValue(controls, self)) 87 | if result is not NotImplemented: 88 | return result 89 | 90 | if isinstance(other, int): 91 | qp.arithmetic_mod.do_plus_const_mod( 92 | lvalue=self[:], 93 | offset=other, 94 | modulus=self.modulus, 95 | control=controls, 96 | forward=False) 97 | return self 98 | 99 | if isinstance(other, (qp.Quint, qp.RValue)): 100 | other, controls = qp.ControlledRValue.split(other) 101 | qp.arithmetic_mod.do_plus_mod( 102 | lvalue=self[:], 103 | offset=other, 104 | modulus=self.modulus, 105 | control=controls, 106 | forward=False) 107 | return self 108 | 109 | return NotImplemented 110 | 111 | def __str__(self): 112 | return '{} (mod {})'.format(self.qureg, self.modulus) 113 | 114 | def __repr__(self): 115 | return 'qp.QuintMod({!r}, {!r})'.format(self.qureg, self.modulus) 116 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/quint_mod_test.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def test_iadd(): 5 | def f(a: qp.QuintMod, b: int): 6 | a += b 7 | 8 | r = qp.testing.sim_call( 9 | f, 10 | a=qp.testing.ModInt(5, 12), 11 | b=9, 12 | ) 13 | assert r == qp.ArgsAndKwargs([], { 14 | 'a': 2, 15 | 'b': 9 16 | }) 17 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/quint_test.py: -------------------------------------------------------------------------------- 1 | import cirq 2 | import pytest 3 | 4 | import quantumpseudocode as qp 5 | 6 | 7 | def test_init(): 8 | q1 = qp.Quint(qp.NamedQureg('test', 10)) 9 | q2 = qp.Quint(qp.NamedQureg('test', 10)) 10 | assert q1.qureg == q2.qureg 11 | assert str(q1) == str(q2) == 'test' 12 | 13 | 14 | def test_len_getitem(): 15 | h = 'test' 16 | q = qp.Quint(qp.NamedQureg(h, 10)) 17 | assert len(q) == 10 18 | 19 | with pytest.raises(IndexError): 20 | _ = q[-100] 21 | 22 | assert q[0] == qp.Qubit(h, 0) 23 | assert q[-1] == qp.Qubit(h, 9) 24 | assert q[2:4].qureg == qp.Quint(qp.RangeQureg( 25 | qp.NamedQureg(h, 10), range(2, 4))).qureg 26 | 27 | 28 | def test_set_item_blocks(): 29 | q = qp.Quint(qp.NamedQureg('test', 10)) 30 | 31 | with pytest.raises(NotImplementedError): 32 | q[2] = qp.Qubit() 33 | 34 | 35 | def test_mul_rmul(): 36 | q = qp.Quint(qp.NamedQureg('test', 10)) 37 | 38 | assert q * 5 == 5 * q == qp.ScaledIntRValue(q, 5) 39 | 40 | 41 | def test_ixor(): 42 | q = qp.Quint(qp.NamedQureg('test', 10)) 43 | 44 | with pytest.raises(TypeError): 45 | q ^= None 46 | 47 | with qp.LogCirqCircuit() as circuit: 48 | q ^= 5 49 | cirq.testing.assert_has_diagram(circuit, """ 50 | test[0]: ---X--- 51 | | 52 | test[2]: ---X--- 53 | """, use_unicode_characters=False) 54 | 55 | q2 = qp.Quint(qp.NamedQureg('test2', 5)) 56 | with qp.LogCirqCircuit() as circuit: 57 | q ^= q2 58 | cirq.testing.assert_has_diagram(circuit, """ 59 | test2[0]: ---@------------------- 60 | | 61 | test2[1]: ---|---@--------------- 62 | | | 63 | test2[2]: ---|---|---@----------- 64 | | | | 65 | test2[3]: ---|---|---|---@------- 66 | | | | | 67 | test2[4]: ---|---|---|---|---@--- 68 | | | | | | 69 | test[0]: ----X---|---|---|---|--- 70 | | | | | 71 | test[1]: --------X---|---|---|--- 72 | | | | 73 | test[2]: ------------X---|---|--- 74 | | | 75 | test[3]: ----------------X---|--- 76 | | 77 | test[4]: --------------------X--- 78 | """, use_unicode_characters=False) 79 | 80 | q3 = qp.Quint(qp.NamedQureg('test3', 5)) 81 | c = qp.Qubit('c') 82 | with qp.LogCirqCircuit() as circuit: 83 | q ^= q3 & qp.controlled_by(c) 84 | cirq.testing.assert_has_diagram(circuit, """ 85 | c: ----------@---@---@---@---@--- 86 | | | | | | 87 | test3[0]: ---@---|---|---|---|--- 88 | | | | | | 89 | test3[1]: ---|---@---|---|---|--- 90 | | | | | | 91 | test3[2]: ---|---|---@---|---|--- 92 | | | | | | 93 | test3[3]: ---|---|---|---@---|--- 94 | | | | | | 95 | test3[4]: ---|---|---|---|---@--- 96 | | | | | | 97 | test[0]: ----X---|---|---|---|--- 98 | | | | | 99 | test[1]: --------X---|---|---|--- 100 | | | | 101 | test[2]: ------------X---|---|--- 102 | | | 103 | test[3]: ----------------X---|--- 104 | | 105 | test[4]: --------------------X--- 106 | """, use_unicode_characters=False) 107 | 108 | # Classes can specify custom behavior via __rixor__. 109 | class Rixor: 110 | def __rixor__(self, other): 111 | qp.phase_flip() 112 | return other 113 | with qp.capture() as out: 114 | q ^= Rixor() 115 | assert out == [('phase_flip', qp.QubitIntersection.ALWAYS)] 116 | 117 | 118 | def test_iadd_isub(): 119 | q = qp.Quint(qp.NamedQureg('test', 10)) 120 | 121 | with pytest.raises(TypeError): 122 | q += None 123 | 124 | with qp.RandomSim(measure_bias=0.5): 125 | with qp.capture() as out: 126 | q += 5 127 | assert qp.ccz_count(out) == 18 128 | 129 | with qp.RandomSim(measure_bias=0.5): 130 | with qp.capture() as out: 131 | q += 4 132 | assert qp.ccz_count(out) == 14 133 | 134 | with qp.RandomSim(measure_bias=0.5): 135 | with qp.capture() as out: 136 | q -= 3 137 | assert qp.ccz_count(out) == 18 138 | 139 | q2 = qp.Quint(qp.NamedQureg('test2', 5)) 140 | with qp.RandomSim(measure_bias=0.5): 141 | with qp.capture() as out: 142 | q += q2 143 | assert qp.ccz_count(out) == 18 144 | 145 | # Classes can specify custom behavior via __riadd__. 146 | class Riadd: 147 | def __riadd__(self, other): 148 | qp.phase_flip() 149 | return other 150 | with qp.capture() as out: 151 | q += Riadd() 152 | assert out == [('phase_flip', qp.QubitIntersection.ALWAYS)] 153 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/qureg.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Iterable, Union 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | class Qureg: 9 | def __len__(self): 10 | raise NotImplementedError() 11 | 12 | def __getitem__(self, item): 13 | raise NotImplementedError() 14 | 15 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 16 | if not allow_mutate: 17 | return [q.resolve(sim_state, False) for q in self] 18 | return sim_state.quint_buf(qp.Quint(self)) 19 | 20 | 21 | @cirq.value_equality 22 | class RawQureg(Qureg): 23 | def __init__(self, qubits: Iterable['qp.Qubit']): 24 | self.qubits = tuple(qubits) 25 | 26 | def _value_equality_values_(self): 27 | return self.qubits 28 | 29 | def __len__(self): 30 | return len(self.qubits) 31 | 32 | def __getitem__(self, item): 33 | r = range(len(self))[item] 34 | if isinstance(r, range): 35 | return RangeQureg(self, r) 36 | return self.qubits[r] 37 | 38 | def __str__(self): 39 | return '[{}]'.format(', '.join(str(e) for e in self.qubits)) 40 | 41 | def __repr__(self): 42 | return 'qp.RawQureg({!r})'.format(self.qubits) 43 | 44 | 45 | @cirq.value_equality 46 | class NamedQureg(Qureg): 47 | def __init__(self, name: str, length: int): 48 | self.name = name 49 | self.length = length 50 | 51 | def _value_equality_values_(self): 52 | return self.name, self.length 53 | 54 | def __len__(self): 55 | return self.length 56 | 57 | def __getitem__(self, item): 58 | r = range(self.length)[item] 59 | if isinstance(r, int): 60 | if r == 0 and self.length == 1: 61 | return qp.Qubit(self.name, None) 62 | return qp.Qubit(self.name, r) 63 | if isinstance(r, range): 64 | return RangeQureg(self, r) 65 | return NotImplemented 66 | 67 | def __repr__(self): 68 | return 'qp.NamedQureg({!r}, {!r})'.format(self.name, self.length) 69 | 70 | def __str__(self): 71 | return str(self.name) 72 | 73 | 74 | @cirq.value_equality 75 | class RangeQureg(Qureg): 76 | def __new__(cls, sub: Qureg, index_range: range): 77 | if (index_range.start == 0 and 78 | index_range.stop == len(sub) and 79 | index_range.step == 1): 80 | return sub 81 | return super().__new__(cls) 82 | 83 | def __init__(self, sub: Qureg, index_range: range): 84 | self.sub = sub 85 | self.range = index_range 86 | 87 | def _value_equality_values_(self): 88 | return self.sub, self.range 89 | 90 | def __len__(self): 91 | return len(self.range) 92 | 93 | def __getitem__(self, item): 94 | r = self.range[item] 95 | if isinstance(r, int): 96 | return self.sub[r] 97 | if isinstance(r, range): 98 | return RangeQureg(self.sub, r) 99 | return NotImplemented 100 | 101 | def __repr__(self): 102 | return 'qp.RangeQureg({!r}, {!r})'.format(self.sub, self.range) 103 | 104 | def __str__(self): 105 | return '{}[{}:{}{}]'.format( 106 | self.sub, 107 | '' if self.range.start == 0 else self.range.start, 108 | '' if self.range.stop == len(self.sub) else self.range.stop, 109 | '' if self.range.step == 1 else ':{}'.format(self.range.step)) 110 | -------------------------------------------------------------------------------- /quantumpseudocode/lvalue/qureg_test.py: -------------------------------------------------------------------------------- 1 | import cirq 2 | import pytest 3 | 4 | import quantumpseudocode as qp 5 | 6 | 7 | def test_raw_qureg_init(): 8 | eq = cirq.testing.EqualsTester() 9 | a = qp.Qubit('a') 10 | b = qp.Qubit('b') 11 | eq.add_equality_group(qp.RawQureg([a, b]), qp.RawQureg([a, b])) 12 | eq.add_equality_group(qp.RawQureg([b, a])) 13 | eq.add_equality_group(qp.RawQureg([])) 14 | 15 | 16 | def test_raw_qureg_getitem_len(): 17 | a = qp.Qubit() 18 | b = qp.Qubit() 19 | q = qp.RawQureg([a, b]) 20 | assert len(q) == 2 21 | assert q[0] == a 22 | assert q[:] == q 23 | assert q[0:2] == q 24 | 25 | 26 | def test_raw_qureg_repr(): 27 | cirq.testing.assert_equivalent_repr( 28 | qp.RawQureg([qp.Qubit()]), 29 | setup_code='import quantumpseudocode as qp' 30 | ) 31 | 32 | 33 | def test_named_qureg_init(): 34 | eq = cirq.testing.EqualsTester() 35 | 36 | q1 = qp.NamedQureg('test', 10) 37 | q2 = qp.NamedQureg('test', 10) 38 | assert str(q1) == str(q2) == 'test' 39 | 40 | eq.add_equality_group(qp.NamedQureg('', 5)) 41 | eq.add_equality_group(qp.NamedQureg('', 6)) 42 | eq.add_equality_group(q1, q2) 43 | eq.add_equality_group(qp.NamedQureg('q', 2)) 44 | 45 | 46 | def test_named_qureg_get_item_len(): 47 | h = 'a' 48 | q = qp.NamedQureg(h, 5) 49 | assert q[0] == qp.Qubit(h, 0) 50 | assert len(q) == 5 51 | assert q[:] == q 52 | assert q[2:4] == qp.RangeQureg(q, range(2, 4)) 53 | 54 | 55 | def test_named_qureg_repr(): 56 | cirq.testing.assert_equivalent_repr( 57 | qp.NamedQureg('a', 3), 58 | setup_code='import quantumpseudocode as qp') 59 | 60 | 61 | def test_range_qureg_init(): 62 | eq = cirq.testing.EqualsTester() 63 | 64 | a = qp.NamedQureg('a', 5) 65 | b = qp.NamedQureg('b', 5) 66 | eq.add_equality_group(a[:2]) 67 | eq.add_equality_group(a[:3]) 68 | eq.add_equality_group(b[:3]) 69 | 70 | 71 | def test_range_qureg_getitem_len(): 72 | h = 'a' 73 | a = qp.NamedQureg(h, 5) 74 | r = qp.RangeQureg(a, range(1, 3)) 75 | assert r[0] == qp.Qubit(h, 1); 76 | assert r[1] == qp.Qubit(h, 2); 77 | assert r[-1] == qp.Qubit(h, 2); 78 | with pytest.raises(IndexError): 79 | _ = r[2] 80 | 81 | 82 | def test_range_qureg_repr(): 83 | h = 'a' 84 | a = qp.NamedQureg(h, 5) 85 | r = qp.RangeQureg(a, range(1, 3)) 86 | cirq.testing.assert_equivalent_repr( 87 | r, 88 | setup_code='import quantumpseudocode as qp') 89 | -------------------------------------------------------------------------------- /quantumpseudocode/ops/__init__.py: -------------------------------------------------------------------------------- 1 | from .operation import ( 2 | ClassicalSimState, 3 | ) 4 | 5 | from .semi_quantum import ( 6 | semi_quantum, 7 | ) 8 | -------------------------------------------------------------------------------- /quantumpseudocode/ops/operation.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Union, Any, Callable 3 | 4 | import quantumpseudocode as qp 5 | 6 | 7 | class ClassicalSimState: 8 | phase_degrees: float 9 | 10 | def measurement_based_uncomputation_result_chooser(self) -> Callable[[], bool]: 11 | raise NotImplementedError() 12 | 13 | def quint_buf(self, quint: 'qp.Quint') -> 'qp.IntBuf': 14 | raise NotImplementedError() 15 | 16 | def resolve_location(self, loc: Any, allow_mutate: bool) -> Any: 17 | pass 18 | -------------------------------------------------------------------------------- /quantumpseudocode/ops/semi_quantum.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import inspect 3 | from typing import Union, Callable, get_type_hints, ContextManager, Dict, List, Optional, Any, NamedTuple 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | def semi_quantum(func: Callable = None, 9 | *, 10 | alloc_prefix: Optional[str] = None, 11 | classical: Callable = None) -> Callable: 12 | """Decorator that allows sending classical values and RValues into a function expecting quantum values. 13 | 14 | Args: 15 | func: The function to decorate. This function must have type annotations for its parameters. The type 16 | annotations are used to define how incoming values are wrapped. For example, a `qp.Qubit.Borrowed` can 17 | accept a `qp.Qubit`, a raw `bool`, or any `qp.RValue[int]` (e.g. the expression `a > b` where `a` and `b` 18 | are both quints produces such an rvalue. 19 | alloc_prefix: An optional string argument that determines how temporarily allocated qubits and quints are 20 | named. Defaults to the name of the decorated function with underscores on either side. 21 | classical: A function with the same arguments as `func` (except that a `control: qp.Qubit.Control` parameter 22 | can be omitted), but with types such as `qp.Quint` replaced by `qp.IntBuf`. The understanding is that 23 | the emulator's effect on mutable buffers should be equivalent to the quantum function's effect on 24 | corresponding qubits and quints. 25 | """ 26 | 27 | # If keyword arguments were specified, python invokes the decorator method 28 | # without a `func` argument, then passes `func` into the result. 29 | if func is None: 30 | return lambda deferred_func: semi_quantum(deferred_func, 31 | alloc_prefix=alloc_prefix, 32 | classical=classical) 33 | 34 | if alloc_prefix is None: 35 | alloc_prefix = func.__name__ 36 | if not alloc_prefix.startswith('_'): 37 | alloc_prefix = '_' + alloc_prefix 38 | if not alloc_prefix.endswith('_'): 39 | alloc_prefix = alloc_prefix + '_' 40 | raw_type_hints = inspect.getfullargspec(func).annotations 41 | 42 | # Empty state to be filled in with parameter handling information. 43 | type_string_map: Dict[str, type] = {} 44 | remap_string_map: Dict[str, Callable] = {} 45 | param_strings: List[str] = [] 46 | assignment_strings: List[str] = [] 47 | indent = ' ' 48 | arg_strings: List[str] = [] 49 | forced_keywords = False 50 | resolve_lines: List[str] = [] 51 | resolve_arg_strings: List[str] = [] 52 | 53 | classical_type_hints = get_type_hints(classical) if classical is not None else {} 54 | quantum_sig = inspect.signature(func) 55 | classical_sig = inspect.signature(classical) if classical is not None else None 56 | 57 | # Process each parameter individually. 58 | for parameter in quantum_sig.parameters.values(): 59 | val = parameter.name 60 | t = raw_type_hints[val] 61 | type_string = f'{getattr(t, "__name__", "type")}_{len(type_string_map)}' 62 | type_string_map[type_string] = t 63 | if parameter.default is not inspect.Parameter.empty: 64 | def_val = f'_default_val_{len(type_string_map)}' 65 | def_str = f' = {def_val}' 66 | type_string_map[def_val] = parameter.default 67 | else: 68 | def_str = '' 69 | if classical_sig is not None and val in classical_sig.parameters: 70 | if parameter.default != classical_sig.parameters[val].default: 71 | raise TypeError('Inconsistent default value. Quantum has {}={!r} but classical has {}={!r}'.format( 72 | val, 73 | parameter.default, 74 | val, 75 | classical_sig.parameters[val].default)) 76 | parameter: inspect.Parameter 77 | 78 | # Sending and receiving arguments. 79 | if parameter.kind == inspect.Parameter.KEYWORD_ONLY and not forced_keywords: 80 | forced_keywords = True 81 | param_strings.append('*') 82 | param_strings.append(f'{val}: {type_string}{def_str}') 83 | arg_string = f'{val}={val}' if forced_keywords else val 84 | arg_strings.append(arg_string) 85 | 86 | # Transforming arguments. 87 | semi_data = TYPE_TO_SEMI_DATA.get(t, None) 88 | if semi_data is not None: 89 | holder = semi_data.context_manager_func 90 | if holder is not None: 91 | remap_string_map[holder.__name__] = holder 92 | assignment_strings.append( 93 | f'{indent}with {holder.__name__}({val}, {repr(alloc_prefix + val)}) as {val}:') 94 | indent += ' ' 95 | 96 | exposer = semi_data.transform_func 97 | if exposer is not None: 98 | remap_string_map[exposer.__name__] = exposer 99 | assignment_strings.append(f'{indent}{val} = {exposer.__name__}({val})') 100 | 101 | if val in classical_type_hints: 102 | remap_string_map[semi_data.resolve_func.__name__] = semi_data.resolve_func 103 | resolve_lines.append(f' {val} = {semi_data.resolve_func.__name__}(sim_state, {val})') 104 | resolve_arg_strings.append(arg_string) 105 | 106 | # Assemble into a function body. 107 | func_name = f'_decorated_{func.__name__}' 108 | lines = [ 109 | f'def {func_name}({", ".join(param_strings)}):', 110 | *assignment_strings, 111 | f'{indent}return func({", ".join(arg_strings)})' 112 | ] 113 | body = '\n'.join(lines) 114 | 115 | # Evaluate generated function code. 116 | result = _eval_body_func(body, 117 | func, 118 | func_name, 119 | exec_globals={**type_string_map, **remap_string_map, 'func': func, 'qp': qp}) 120 | if classical is not None: 121 | result.classical = classical 122 | if 'control' in raw_type_hints and 'control' not in classical_type_hints: 123 | assert TYPE_TO_SEMI_DATA[raw_type_hints['control']] is TYPE_TO_SEMI_DATA[qp.Qubit.Control] 124 | resolve_lines.insert(0, ' if not sim_state.resolve_location(control):') 125 | resolve_lines.insert(1, ' return') 126 | 127 | new_args = list(classical_type_hints.keys() - raw_type_hints.keys() - {'sim_state'}) 128 | for arg in list(new_args): 129 | if classical_sig.parameters[arg].default is not inspect.Parameter.empty: 130 | new_args.remove(arg) 131 | if new_args: 132 | raise TypeError('classical function cannot introduce new parameters ' 133 | '(besides sim+state and arguments with default values), ' 134 | 'but {} introduced {!r}'.format(classical, new_args)) 135 | missing_args = list(set(raw_type_hints.keys()) - classical_type_hints.keys() - {'control'}) 136 | if missing_args: 137 | raise TypeError('classical function cannot omit parameters (except control), ' 138 | 'but missed {!r}'.format(missing_args)) 139 | 140 | if 'sim_state' in classical_type_hints: 141 | resolve_arg_strings.insert(0, 'sim_state') 142 | 143 | resolve_body = '\n'.join([ 144 | f'def sim(sim_state: qp.ClassicalSimState, {", ".join(param_strings)}):', 145 | *resolve_lines, 146 | f' return classical_func({", ".join(resolve_arg_strings)})' 147 | ]) 148 | 149 | result.sim = _eval_body_func(resolve_body, 150 | classical, 151 | 'sim', 152 | {'classical_func': classical, 'qp': qp, **type_string_map, **remap_string_map}) 153 | 154 | return result 155 | 156 | 157 | def _eval_body_func(body: str, func: Callable, func_name_in_body: str, exec_globals: Dict[str, Any]) -> Callable: 158 | # Evaluate the code. 159 | exec_locals = {} 160 | try: 161 | exec(body, exec_globals, exec_locals) 162 | except Exception as ex: 163 | raise RuntimeError('Failed to build decorator transformation.\n' 164 | '\n' 165 | 'Source:\n' 166 | f'{body}\n' 167 | '\n' 168 | f'Globals: {exec_globals}\n' 169 | f'Locals: {exec_locals}') from ex 170 | 171 | # Make the decorated function look like the original function when inspected. 172 | result = functools.wraps(func)(exec_locals[func_name_in_body]) 173 | result.__doc__ = (result.__doc__ or '') + f'\n\n==== Decorator body ====\n{body}' 174 | 175 | return result 176 | 177 | 178 | def _rval_quint_manager(val: 'qp.Quint.Borrowed', name: str) -> ContextManager['qp.Quint']: 179 | if isinstance(val, qp.Quint): 180 | return qp.EmptyManager(val) 181 | if isinstance(val, (bool, int)): 182 | return qp.HeldRValueManager(qp.IntRValue(int(val)), name=name) 183 | if isinstance(val, qp.RValue): 184 | return qp.HeldRValueManager(val, name=name) 185 | raise TypeError('Expected a classical or quantum integer expression (a quint, int, or RVal[int]) ' 186 | 'but got {!r}.'.format(val)) 187 | 188 | 189 | def _mutable_resolve(sim_state: 'qp.ClassicalSimState', val: Any): 190 | return sim_state.resolve_location(val, allow_mutate=True) 191 | 192 | 193 | def _immutable_resolve(sim_state: 'qp.ClassicalSimState', val: Any): 194 | return sim_state.resolve_location(val, allow_mutate=False) 195 | 196 | 197 | def _lval_quint_checker(val: 'qp.Quint') -> 'qp.Quint': 198 | if not isinstance(val, qp.Quint): 199 | raise TypeError('Expected a qp.Quint but got {!r}'.format(val)) 200 | return val 201 | 202 | 203 | def _rval_qubit_manager(val: 'qp.Qubit.Borrowed', name: str) -> ContextManager['qp.Qubit']: 204 | if isinstance(val, qp.Qubit): 205 | return qp.EmptyManager(val) 206 | if val in [False, True]: 207 | return qp.HeldRValueManager(qp.BoolRValue(bool(val)), name=name) 208 | if isinstance(val, qp.RValue): 209 | return qp.HeldRValueManager(val, name=name) 210 | raise TypeError('Expected a classical or quantum boolean expression (a qubit, bool, or RValue[bool]) ' 211 | 'but got {!r}.'.format(val)) 212 | 213 | 214 | def _lval_qubit_checker(val: 'qp.Qubit') -> 'qp.Qubit': 215 | if not isinstance(val, qp.Qubit): 216 | raise TypeError('Expected a qp.Qubit but got {!r}'.format(val)) 217 | return val 218 | 219 | 220 | def _control_qubit_manager(val: 'qp.Qubit.Control', name: str) -> ContextManager['qp.QubitIntersection']: 221 | if not isinstance(val, qp.Quint): 222 | if val is None or val in [True, 1, qp.QubitIntersection.ALWAYS]: 223 | return qp.EmptyManager(qp.QubitIntersection.ALWAYS) 224 | if val in [False, 0, qp.QubitIntersection.NEVER]: 225 | return qp.EmptyManager(qp.QubitIntersection.NEVER) 226 | if isinstance(val, qp.Qubit): 227 | return qp.EmptyManager(qp.QubitIntersection((val,))) 228 | if isinstance(val, qp.QubitIntersection) and len(val.qubits) == 1: 229 | return qp.EmptyManager(val) 230 | if isinstance(val, qp.RValue): 231 | return qp.HeldRValueManager(val, name=name) 232 | raise TypeError('Expected a quantum control expression (a None, qubit, or RValue[bool]) ' 233 | 'but got {!r}.'.format(val)) 234 | 235 | 236 | def _control_qubit_exposer(val: Union['qp.Qubit', 'qp.QubitIntersection']) -> 'qp.QubitIntersection': 237 | if isinstance(val, qp.Qubit): 238 | return qp.QubitIntersection((val,)) 239 | assert isinstance(val, qp.QubitIntersection) 240 | return val 241 | 242 | 243 | SemiQuantumTypeData = NamedTuple( 244 | 'SemiQuantumType', 245 | [ 246 | ('context_manager_func', Optional[Callable[[Any], ContextManager]]), 247 | ('transform_func', Optional[Callable[[Any], Any]]), 248 | ('resolve_func', Callable[[Any], Any]), 249 | ] 250 | ) 251 | 252 | 253 | def _generate_type_to_transform() -> Dict[type, SemiQuantumTypeData]: 254 | result = { 255 | qp.Quint: SemiQuantumTypeData( 256 | context_manager_func=None, 257 | transform_func=_lval_quint_checker, 258 | resolve_func=_mutable_resolve), 259 | qp.Quint.Borrowed: SemiQuantumTypeData( 260 | context_manager_func=_rval_quint_manager, 261 | transform_func=None, 262 | resolve_func=_immutable_resolve), 263 | qp.Qubit: SemiQuantumTypeData( 264 | context_manager_func=None, 265 | transform_func=_lval_qubit_checker, 266 | resolve_func=_mutable_resolve), 267 | qp.Qubit.Borrowed: SemiQuantumTypeData( 268 | context_manager_func=_rval_qubit_manager, 269 | transform_func=None, 270 | resolve_func=_immutable_resolve), 271 | qp.Qubit.Control: SemiQuantumTypeData( 272 | context_manager_func=_control_qubit_manager, 273 | transform_func=_control_qubit_exposer, 274 | resolve_func=_mutable_resolve), 275 | } 276 | result['qp.Quint'] = result[qp.Quint] 277 | result['qp.Qubit.Borrowed'] = result[qp.Qubit.Borrowed] 278 | result['qp.Qubit.Control'] = result[qp.Qubit.Control] 279 | result['qp.Qubit'] = result[qp.Qubit] 280 | result['qp.Quint.Borrowed'] = result[qp.Quint.Borrowed] 281 | 282 | for t, v in list(result.items()): 283 | def _f() -> t: 284 | pass 285 | t2 = get_type_hints(_f)['return'] 286 | result[t2] = v 287 | return result 288 | 289 | 290 | TYPE_TO_SEMI_DATA = _generate_type_to_transform() 291 | -------------------------------------------------------------------------------- /quantumpseudocode/qalloc.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, Any, overload 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from quantumpseudocode import sink 7 | 8 | 9 | @overload 10 | def qalloc(*, 11 | len: int, 12 | name: Optional[str] = None, 13 | x_basis: bool = False) -> 'qp.Quint': 14 | pass 15 | 16 | 17 | @overload 18 | def qalloc(*, 19 | modulus: int, 20 | name: Optional[str] = None, 21 | x_basis: bool = False) -> 'qp.QuintMod': 22 | pass 23 | 24 | 25 | @overload 26 | def qalloc(*, 27 | name: Optional[str] = None, 28 | x_basis: bool = False) -> 'qp.Qubit': 29 | pass 30 | 31 | 32 | def qalloc(*, 33 | len: Optional[int] = None, 34 | modulus: Optional[int] = None, 35 | name: Optional[str] = None, 36 | x_basis: bool = False) -> 'Any': 37 | """Allocates new quantum objects. 38 | 39 | If no arguments are given, allocates a qubit. 40 | If the `len` argument is given, allocates a quint. 41 | If the `modulus` argument is given, allocates a modular quint. 42 | 43 | Args: 44 | len: Causes a quint to be allocated. Number of qubits in the quint register. 45 | modulus: Causes a modular quint, storing values modulo this modulus, to be allocated. 46 | name: Debug information to associate with the allocated object. 47 | x_basis: If set, the register is initialized into a uniform superposition instead of into the zero state. 48 | 49 | Returns: 50 | A qubit, quint, or modular quint. 51 | """ 52 | if len is None and modulus is None: 53 | n = 1 54 | wrap = lambda e: e[0] 55 | elif len is not None and modulus is None: 56 | n = len 57 | wrap = qp.Quint 58 | elif len is None and modulus is not None: 59 | assert modulus > 0 60 | n = (modulus - 1).bit_length() 61 | wrap = lambda e: qp.QuintMod(e, modulus) 62 | else: 63 | raise ValueError(f'Incompatible argument combination.') 64 | 65 | qureg = sink.global_sink.do_allocate(AllocArgs( 66 | qureg_name=name or '', 67 | qureg_length=n, 68 | x_basis=x_basis)) 69 | 70 | return wrap(qureg) 71 | 72 | 73 | def qfree(target: Union[qp.Qubit, qp.Qureg, qp.Quint], 74 | equivalent_expression: 'Union[None, bool, int, qp.RValue[Any]]' = None, 75 | dirty: bool = False): 76 | """Deallocates quantum objects. 77 | 78 | Args: 79 | target: The quantum object to free. 80 | equivalent_expression: An entangled expression with the same computational basis value as the quantum object. 81 | Used to uncompute the quantum object before freeing it, to avoid revealing information. 82 | dirty: Indicates that the quantum object is not expected to be zero'd. 83 | """ 84 | 85 | if equivalent_expression is not None: 86 | qp.rval(equivalent_expression).clear_storage_location( 87 | target, qp.QubitIntersection.ALWAYS) 88 | 89 | if isinstance(target, qp.Qubit): 90 | assert target.index is None, "Can't deallocate individual qubits from within a register." 91 | reg = qp.NamedQureg(name=target.name, length=1) 92 | elif isinstance(target, qp.Qureg): 93 | reg = target 94 | elif isinstance(target, qp.Quint): 95 | reg = target.qureg 96 | elif isinstance(target, qp.QuintMod): 97 | reg = target.qureg 98 | else: 99 | raise NotImplementedError() 100 | if len(reg): 101 | sink.global_sink.do_release(qp.ReleaseQuregOperation(reg, dirty=dirty)) 102 | 103 | 104 | class AllocArgs: 105 | def __init__(self, 106 | *, 107 | qureg_length: int, 108 | qureg_name: Optional[str] = None, 109 | x_basis: bool = False): 110 | assert qureg_name is None or isinstance(qureg_name, str) 111 | self.qureg_length = qureg_length 112 | self.qureg_name = qureg_name 113 | self.x_basis = x_basis 114 | 115 | 116 | @cirq.value_equality 117 | class ReleaseQuregOperation: 118 | def __init__(self, 119 | qureg: 'qp.Qureg', 120 | x_basis: bool = False, 121 | dirty: bool = False): 122 | self.qureg = qureg 123 | self.x_basis = x_basis 124 | self.dirty = dirty 125 | 126 | def _value_equality_values_(self): 127 | return self.qureg, self.x_basis, self.dirty 128 | 129 | def __str__(self): 130 | return 'RELEASE {} [{}{}]'.format( 131 | self.qureg, 132 | 'X' if self.x_basis else 'Z', 133 | ', dirty' if self.dirty else '') 134 | -------------------------------------------------------------------------------- /quantumpseudocode/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | cirq 3 | pytest 4 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/__init__.py: -------------------------------------------------------------------------------- 1 | from .hold import ( 2 | HeldRValueManager, 3 | hold, 4 | ) 5 | 6 | from .expression import ( 7 | QubitIntersection, 8 | ScaledIntRValue, 9 | ) 10 | 11 | from .backed import ( 12 | QubitRValue, 13 | QuintRValue, 14 | ) 15 | 16 | from .const import ( 17 | BoolRValue, 18 | IntRValue, 19 | ) 20 | 21 | from .rvalue import ( 22 | rval, 23 | RValue, 24 | ) 25 | 26 | from .lookup import ( 27 | LookupTable, 28 | ) 29 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/backed.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, Tuple, Iterable 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from .rvalue import RValue 7 | 8 | 9 | @cirq.value_equality 10 | class QubitRValue(RValue[bool]): 11 | def __init__(self, val: 'qp.Qubit'): 12 | self.val = val 13 | 14 | def trivial_unwrap(self): 15 | return self.val 16 | 17 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 18 | return self.val.resolve(sim_state, False) 19 | 20 | def _value_equality_values_(self): 21 | return self.val 22 | 23 | def existing_storage_location(self) -> Any: 24 | return self.val 25 | 26 | def alloc_storage_location(self, name: Optional[str] = None): 27 | return qp.qalloc(name=name) 28 | 29 | def init_storage_location(self, 30 | location: Any, 31 | controls: 'qp.QubitIntersection'): 32 | location ^= self.val & controls 33 | 34 | def clear_storage_location(self, 35 | location: Any, 36 | controls: 'qp.QubitIntersection'): 37 | with qp.measurement_based_uncomputation(location) as b: 38 | qp.phase_flip(self.val & controls & b) 39 | 40 | def __str__(self): 41 | return 'rval({})'.format(self.val) 42 | 43 | def __repr__(self): 44 | return 'qp.QubitRValue({!r})'.format(self.val) 45 | 46 | 47 | @cirq.value_equality 48 | class QuintRValue(RValue[int]): 49 | def __init__(self, val: 'qp.Quint'): 50 | self.val = val 51 | 52 | def trivial_unwrap(self): 53 | return self.val 54 | 55 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 56 | return self.val.resolve(sim_state, False) 57 | 58 | def _value_equality_values_(self): 59 | return self.val 60 | 61 | def existing_storage_location(self) -> Any: 62 | return self.val 63 | 64 | def alloc_storage_location(self, name: Optional[str] = None): 65 | return qp.qalloc(len=len(self.val), name=name) 66 | 67 | def init_storage_location(self, 68 | location: Any, 69 | controls: 'qp.QubitIntersection'): 70 | qp.arithmetic.do_xor(lvalue=location, mask=self.val, control=controls) 71 | 72 | def clear_storage_location(self, 73 | location: Any, 74 | controls: 'qp.QubitIntersection'): 75 | qp.arithmetic.do_xor(lvalue=location, mask=self.val, control=controls) 76 | 77 | def __str__(self): 78 | return 'rval({})'.format(self.val) 79 | 80 | def __repr__(self): 81 | return 'qp.QuintRValue({!r})'.format(self.val) 82 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/const.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, Tuple, Iterable 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from .rvalue import RValue 7 | 8 | 9 | @cirq.value_equality 10 | class BoolRValue(RValue[bool]): 11 | def __init__(self, val: bool): 12 | self.val = val 13 | 14 | def trivial_unwrap(self): 15 | return self.val 16 | 17 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 18 | return self.val 19 | 20 | def _value_equality_values_(self): 21 | return self.val 22 | 23 | def alloc_storage_location(self, name: Optional[str] = None): 24 | return qp.qalloc(name=name) 25 | 26 | def init_storage_location(self, 27 | location: 'qp.Qubit', 28 | controls: 'qp.QubitIntersection'): 29 | location ^= self.val & qp.controlled_by(controls) 30 | 31 | def clear_storage_location(self, 32 | location: 'qp.Qubit', 33 | controls: 'qp.QubitIntersection'): 34 | with qp.measurement_based_uncomputation(location) as b: 35 | if self.val and b: 36 | qp.phase_flip(controls) 37 | 38 | def __str__(self): 39 | return 'rval({})'.format(self.val) 40 | 41 | def __repr__(self): 42 | return 'qp.BoolRValue({!r})'.format(self.val) 43 | 44 | 45 | @cirq.value_equality 46 | class IntRValue(RValue[bool]): 47 | def __init__(self, val: int): 48 | self.val = val 49 | 50 | def trivial_unwrap(self): 51 | return self.val 52 | 53 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 54 | return self.val 55 | 56 | def _value_equality_values_(self): 57 | return self.val 58 | 59 | def alloc_storage_location(self, name: Optional[str] = None): 60 | return qp.qalloc(len=self.val.bit_length(), name=name) 61 | 62 | def __int__(self): 63 | return self.val 64 | 65 | def init_storage_location(self, 66 | location: 'qp.Quint', 67 | controls: 'qp.QubitIntersection'): 68 | location ^= self.val & qp.controlled_by(controls) 69 | 70 | def clear_storage_location(self, 71 | location: 'qp.Quint', 72 | controls: 'qp.QubitIntersection'): 73 | with qp.measurement_based_uncomputation(location) as r: 74 | if qp.popcnt(r & self.val) & 1: 75 | qp.phase_flip(controls) 76 | 77 | def __riadd__(self, other): 78 | other, controls = qp.ControlledLValue.split(other) 79 | if controls == qp.QubitIntersection.NEVER: 80 | return other 81 | 82 | if isinstance(other, qp.Quint): 83 | if self.val == 0: 84 | return other 85 | k = qp.leading_zero_bit_count(self.val) 86 | qp.arithmetic.do_addition( 87 | lvalue=other[k:], 88 | offset=self.val >> k, 89 | carry_in=False, 90 | control=controls) 91 | return other 92 | return NotImplemented 93 | 94 | def __rimul__(self, other): 95 | other, controls = qp.ControlledLValue.split(other) 96 | if isinstance(other, qp.Quint): 97 | qp.arithmetic.do_multiplication(lvalue=other, factor=self.val, control=controls) 98 | return other 99 | return NotImplemented 100 | 101 | def __str__(self): 102 | return f'rval({self.val})' 103 | 104 | def __repr__(self): 105 | return f'qp.IntRValue({self.val!r})' 106 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/expression.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple, Iterable, Any 2 | 3 | import cirq 4 | 5 | import quantumpseudocode as qp 6 | from .rvalue import RValue 7 | from quantumpseudocode import sink 8 | 9 | 10 | @cirq.value_equality 11 | class ScaledIntRValue(RValue[int]): 12 | """An rvalue for expressions like `quint * int`.""" 13 | 14 | def __init__(self, coherent: 'qp.Quint', constant: int): 15 | self.coherent = coherent 16 | self.constant = constant 17 | 18 | def _value_equality_values_(self): 19 | return self.coherent, self.constant 20 | 21 | def __str__(self): 22 | return 'rval({} * {})'.format(self.coherent, self.constant) 23 | 24 | def __repr__(self): 25 | return 'qp.ScaledIntRValue({!r}, {!r})'.format( 26 | self.coherent, self.constant) 27 | 28 | def __riadd__(self, other): 29 | other, controls = qp.ControlledLValue.split(other) 30 | if controls == qp.QubitIntersection.NEVER: 31 | return other 32 | 33 | if isinstance(other, qp.Quint): 34 | qp.arithmetic.do_plus_product( 35 | lvalue=other, 36 | quantum_factor=self.coherent, 37 | const_factor=self.constant, 38 | control=controls, 39 | ) 40 | return other 41 | 42 | return NotImplemented 43 | 44 | def __risub__(self, other): 45 | other, controls = qp.ControlledLValue.split(other) 46 | if controls == qp.QubitIntersection.NEVER: 47 | return other 48 | 49 | if isinstance(other, qp.Quint): 50 | qp.arithmetic.do_plus_product( 51 | lvalue=other, 52 | quantum_factor=self.coherent, 53 | const_factor=self.constant, 54 | control=controls, 55 | forward=False, 56 | ) 57 | return other 58 | 59 | return NotImplemented 60 | 61 | def alloc_storage_location(self, name: Optional[str] = None): 62 | return qp.qalloc(len=len(self.coherent) + self.constant.bit_length(), name=name) 63 | 64 | def init_storage_location(self, 65 | location: 'qp.Quint', 66 | controls: 'qp.QubitIntersection'): 67 | qp.arithmetic.do_plus_product( 68 | control=controls, 69 | lvalue=location, 70 | quantum_factor=self.coherent, 71 | const_factor=self.constant, 72 | ) 73 | 74 | def clear_storage_location(self, 75 | location: Any, 76 | controls: 'qp.QubitIntersection'): 77 | qp.arithmetic.do_plus_product( 78 | control=controls, 79 | lvalue=location, 80 | quantum_factor=self.coherent, 81 | const_factor=self.constant, 82 | forward=False 83 | ) 84 | 85 | 86 | @cirq.value_equality 87 | class QubitIntersection(RValue[bool]): 88 | """The logical-and of several qubits and bits.""" 89 | 90 | ALWAYS = None # type: QubitIntersection 91 | NEVER = None # type: QubitIntersection 92 | 93 | def __init__(self, qubits: Tuple['qp.Qubit', ...] = (), bit: bool = True): 94 | assert all(isinstance(e, qp.Qubit) for e in qubits) 95 | self.qubits = tuple(qubits) if bit else () 96 | assert len(self.qubits) == len(set(self.qubits)) 97 | self.bit = bool(bit) 98 | 99 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool) -> bool: 100 | v = qp.Quint(qp.RawQureg(self.qubits)).resolve(sim_state, False) 101 | return self.bit and v == (1 << len(self.qubits)) - 1 102 | 103 | @staticmethod 104 | def try_from(val: Any) -> Optional['qp.QubitIntersection']: 105 | if isinstance(val, (bool, int)): 106 | if val in [False, 0]: 107 | return qp.QubitIntersection.NEVER 108 | 109 | if val in [True, 1]: 110 | return qp.QubitIntersection.ALWAYS 111 | 112 | if isinstance(val, qp.Qubit): 113 | return qp.QubitIntersection((val,)) 114 | 115 | if isinstance(val, qp.QubitIntersection): 116 | return val 117 | 118 | return None 119 | 120 | def _value_equality_values_(self): 121 | return self.bit and frozenset(self.qubits) 122 | 123 | def __rand__(self, other): 124 | return self.__and__(other) 125 | 126 | def __and__(self, other): 127 | if isinstance(other, QubitIntersection): 128 | return QubitIntersection(self.qubits + other.qubits, bit=self.bit and other.bit) 129 | if isinstance(other, qp.Qubit): 130 | return QubitIntersection(self.qubits + (other,), bit=self.bit) 131 | if other in [False, 0]: 132 | return qp.QubitIntersection.NEVER 133 | if other in [True, 1]: 134 | return self 135 | return NotImplemented 136 | 137 | def alloc_storage_location(self, name: Optional[str] = None): 138 | return qp.qalloc(name=name) 139 | 140 | def init_storage_location(self, 141 | location: 'qp.Qubit', 142 | controls: 'qp.QubitIntersection'): 143 | location ^= self & controls 144 | 145 | def clear_storage_location(self, 146 | location: Any, 147 | controls: 'qp.QubitIntersection'): 148 | with qp.measurement_based_uncomputation(location) as b: 149 | qp.phase_flip(self & controls & b) 150 | 151 | def __str__(self): 152 | if not self.bit: 153 | return 'never' 154 | if not self.qubits: 155 | return 'always' 156 | return ' & '.join(str(e) for e in self.qubits) 157 | 158 | def __repr__(self): 159 | if not self.bit: 160 | return 'qp.QubitIntersection(bit=False)' 161 | return 'qp.QubitIntersection({!r})'.format(self.qubits) 162 | 163 | 164 | QubitIntersection.ALWAYS = QubitIntersection() 165 | QubitIntersection.NEVER = QubitIntersection(bit=False) 166 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/expression_test.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import cirq 4 | import pytest 5 | 6 | import quantumpseudocode as qp 7 | 8 | 9 | def test_let_and_circuit(): 10 | with qp.Sim(enforce_release_at_zero=False): 11 | with qp.LogCirqCircuit() as circuit: 12 | with qp.qalloc(len=3, name='q') as q: 13 | q[0].init(q[1] & q[2]) 14 | 15 | cirq.testing.assert_has_diagram(circuit, r""" 16 | q[0]: ---alloc---X---release--- 17 | | | | 18 | q[1]: ---alloc---@---release--- 19 | | | | 20 | q[2]: ---alloc---@---release--- 21 | """, use_unicode_characters=False) 22 | 23 | 24 | def test_del_and_circuit(): 25 | with qp.Sim(enforce_release_at_zero=False, phase_fixup_bias=True): 26 | with qp.LogCirqCircuit() as circuit: 27 | with qp.qalloc(len=3, name='q') as q: 28 | q[0].clear(q[1] & q[2]) 29 | 30 | cirq.testing.assert_has_diagram(circuit, r""" 31 | q[0]: ---alloc---Mxc-------cxM---release--- 32 | | | 33 | q[1]: ---alloc---------@---------release--- 34 | | | | 35 | q[2]: ---alloc---------Z---------release--- 36 | """, use_unicode_characters=False) 37 | 38 | with qp.Sim(enforce_release_at_zero=False, phase_fixup_bias=False): 39 | with qp.LogCirqCircuit() as circuit: 40 | with qp.qalloc(len=3, name='b') as q: 41 | q[0].clear(q[1] & q[2]) 42 | 43 | cirq.testing.assert_has_diagram(circuit, r""" 44 | b[0]: ---alloc---Mxc---cxM---release--- 45 | | | 46 | b[1]: ---alloc---------------release--- 47 | | | 48 | b[2]: ---alloc---------------release--- 49 | """, use_unicode_characters=False) 50 | 51 | 52 | def test_uncompute(): 53 | for a, b, p in itertools.product([False, True], repeat=3): 54 | with qp.Sim(phase_fixup_bias=p): 55 | with qp.hold(a, name='a') as qa: 56 | with qp.hold(b, name='b') as qb: 57 | with qp.hold(a & b, name='c') as qc: 58 | assert qp.measure(qa) == a 59 | assert qp.measure(qb) == b 60 | assert qp.measure(qc) == (a and b) 61 | 62 | 63 | def test_intersection_and(): 64 | a = qp.Qubit('a') 65 | b = qp.Qubit('b') 66 | c = qp.Qubit('c') 67 | d = qp.Qubit('d') 68 | assert a & b & c == qp.QubitIntersection((a, b, c)) 69 | assert a & b & c & d == qp.QubitIntersection((a, b, c, d)) 70 | assert (a & b) & c == a & (b & c) 71 | assert (a & b) & (c & d) == a & (b & (c & d)) 72 | 73 | assert (a & b) & False == qp.QubitIntersection.NEVER 74 | assert False & (a & b) == qp.QubitIntersection.NEVER 75 | assert True & (a & b) == a & b 76 | 77 | 78 | # HACK: workaround qubit name lifetime issues by hiding inside lambdas. 79 | @pytest.mark.parametrize('value', [ 80 | lambda: qp.QubitIntersection.NEVER, 81 | lambda: qp.QubitIntersection.ALWAYS, 82 | lambda: qp.QubitIntersection((qp.Qubit('a'),)), 83 | lambda: qp.QubitIntersection((qp.Qubit('a'), qp.Qubit('b'))), 84 | ]) 85 | def test_intersection_repr(value): 86 | cirq.testing.assert_equivalent_repr( 87 | value(), 88 | setup_code='import quantumpseudocode as qp') 89 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/hold.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, Union, Generic, TypeVar, List, Tuple, Iterable, overload 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | T = TypeVar('T') 7 | 8 | 9 | @overload 10 | def hold(val: 'qp.Qubit', 11 | name: Optional[str] = None, 12 | controls: 'Optional[qp.QubitIntersection]' = None) -> 'qp.HeldRValueManager[bool]': 13 | pass 14 | 15 | 16 | @overload 17 | def hold(val: 'qp.Quint', 18 | name: Optional[str] = None, 19 | controls: 'Optional[qp.QubitIntersection]' = None) -> 'qp.HeldRValueManager[int]': 20 | pass 21 | 22 | 23 | @overload 24 | def hold(val: 'int', 25 | name: Optional[str] = None, 26 | controls: 'Optional[qp.QubitIntersection]' = None) -> 'qp.HeldRValueManager[int]': 27 | pass 28 | 29 | 30 | @overload 31 | def hold(val: 'bool', 32 | name: Optional[str] = None, 33 | controls: 'Optional[qp.QubitIntersection]' = None) -> 'qp.HeldRValueManager[bool]': 34 | pass 35 | 36 | 37 | @overload 38 | def hold(val: 'qp.RValue[T]', 39 | name: Optional[str] = None, 40 | controls: 'Optional[qp.QubitIntersection]' = None) -> 'qp.HeldRValueManager[T]': 41 | pass 42 | 43 | 44 | def hold(val: Union[T, 'qp.RValue[T]', 'qp.Qubit', 'qp.Quint'], 45 | *, 46 | name: str = '', 47 | controls: 'Optional[qp.QubitIntersection]' = None 48 | ) -> 'qp.HeldRValueManager[T]': 49 | """Returns a context manager that ensures the given rvalue is allocated. 50 | 51 | Usage: 52 | ``` 53 | with qp.hold(5) as reg: 54 | # reg is an allocated quint storing the value 5 55 | ... 56 | ``` 57 | 58 | Args: 59 | val: The value to hold. 60 | name: Optional name to use when allocating space for the value. 61 | controls: If any of these are not set, the result is a default value 62 | (e.g. False or 0) instead of the rvalue. 63 | 64 | Returns: 65 | A qp.HeldRValueManager wrapping the given value. 66 | """ 67 | return qp.HeldRValueManager( 68 | qp.rval(val), 69 | controls=qp.QubitIntersection.ALWAYS if controls is None else controls, 70 | name=name) 71 | 72 | 73 | class HeldRValueManager(Generic[T]): 74 | def __init__(self, rvalue: 'qp.RValue[T]', 75 | *, 76 | controls: 'qp.QubitIntersection' = None, 77 | name: str = ''): 78 | assert isinstance(name, str) 79 | self.name = name 80 | self.rvalue = rvalue 81 | self.controls = controls if controls is not None else qp.QubitIntersection.ALWAYS 82 | self.location = None # type: Optional[Any] 83 | self.qalloc = None # type: Optional[Any] 84 | 85 | def __repr__(self): 86 | return 'qp.HeldRValueManager({!r}, {!r})'.format( 87 | self.rvalue, self.name) 88 | 89 | def __enter__(self): 90 | assert self.location is None 91 | self.location = self.rvalue.existing_storage_location() 92 | if self.location is None: 93 | self.location = self.rvalue.alloc_storage_location(self.name) 94 | self.qalloc = self.location 95 | self.qalloc.__enter__() 96 | self.rvalue.init_storage_location(self.location, self.controls) 97 | return self.location 98 | 99 | def __exit__(self, exc_type, exc_val, exc_tb): 100 | if self.qalloc is not None and exc_type is None: 101 | self.rvalue.clear_storage_location(self.location, self.controls) 102 | self.qalloc.__exit__(exc_type, exc_val, exc_tb) 103 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/lookup.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Union, Iterable, Iterator 3 | 4 | import quantumpseudocode as qp 5 | 6 | 7 | def _flatten(x: Union[int, Iterable]) -> Iterator[int]: 8 | if isinstance(x, int): 9 | yield x 10 | else: 11 | for item in x: 12 | yield from _flatten(item) 13 | 14 | 15 | class LookupTable: 16 | """A classical list that supports quantum addressing.""" 17 | 18 | def __init__(self, 19 | values: Union[Iterable[int], Iterable[Iterable[int]]]): 20 | self.values = tuple(_flatten(values)) 21 | assert len(self.values) > 0 22 | assert all(e >= 0 for e in self.values) 23 | 24 | def output_len(self) -> int: 25 | return max(e.bit_length() for e in self.values) 26 | 27 | @staticmethod 28 | def random(addresses: Union[int, range, Iterable[int]], 29 | word_size: Union[int, range, Iterable[int]]) -> 'LookupTable': 30 | """Generates a LookupTable with random contents of specified lengths.""" 31 | addresses = addresses if isinstance(addresses, int) else random.choice(addresses) 32 | word_size = word_size if isinstance(word_size, int) else random.choice(word_size) 33 | return LookupTable([ 34 | random.randint(0, 2**word_size - 1) 35 | for _ in range(addresses) 36 | ]) 37 | 38 | def __len__(self): 39 | return len(self.values) 40 | 41 | def __getitem__(self, item): 42 | if isinstance(item, bool): 43 | return self.values[int(item)] 44 | if isinstance(item, int): 45 | return self.values[item] 46 | if isinstance(item, slice): 47 | return LookupTable(self.values[item]) 48 | if isinstance(item, tuple): 49 | if all(isinstance(e, qp.Quint) for e in item): 50 | reg = qp.RawQureg(q for e in item[::-1] for q in e) 51 | return qp.LookupRValue(self, qp.Quint(reg)) 52 | if isinstance(item, qp.Quint): 53 | return qp.LookupRValue(self, item) 54 | if isinstance(item, qp.Qubit): 55 | return qp.LookupRValue(self, qp.Quint(item.qureg)) 56 | raise NotImplementedError('Strange index: {}'.format(item)) 57 | 58 | def __repr__(self): 59 | return 'qp.LookupTable({!r})'.format(self.values) 60 | -------------------------------------------------------------------------------- /quantumpseudocode/rvalue/rvalue.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional, Any, Generic, TypeVar, overload 3 | 4 | import quantumpseudocode as qp 5 | 6 | T = TypeVar('T') 7 | 8 | 9 | class RValue(Generic[T], metaclass=abc.ABCMeta): 10 | """A value or expression that only needs to exist temporarily.""" 11 | 12 | def trivial_unwrap(self): 13 | """Returns the value wrapped by this RValue, if it already exists. 14 | 15 | For example, an rvalue wrapping a classical integer will unwrap into that integers. 16 | Similarly, an rvalue wrapping a quantum integer register unwrap into that quantum integer. 17 | But an expression rvalue, such as a comparison, will unwrap to itself. 18 | """ 19 | return self 20 | 21 | def existing_storage_location(self) -> Any: 22 | """If the rvalue references a storage location, returns that location. Otherwise returns None.""" 23 | return None 24 | 25 | @abc.abstractmethod 26 | def alloc_storage_location(self, name: Optional[str] = None) -> Any: 27 | """Creates a new storage location that the rvalue can be stored in.""" 28 | pass 29 | 30 | @abc.abstractmethod 31 | def init_storage_location(self, 32 | location: Any, 33 | controls: 'qp.QubitIntersection'): 34 | """Initializes a zero'd storage location so that it contains the rvalue.""" 35 | pass 36 | 37 | @abc.abstractmethod 38 | def clear_storage_location(self, 39 | location: Any, 40 | controls: 'qp.QubitIntersection'): 41 | """Zeroes a storage location that currently contains the rvalue.""" 42 | pass 43 | 44 | 45 | _raise_on_fail = object() 46 | 47 | 48 | @overload 49 | def rval(val: 'qp.Qubit') -> 'qp.RValue[bool]': 50 | pass 51 | 52 | 53 | @overload 54 | def rval(val: 'qp.Quint') -> 'qp.RValue[int]': 55 | pass 56 | 57 | 58 | @overload 59 | def rval(val: 'int') -> 'qp.RValue[int]': 60 | pass 61 | 62 | 63 | @overload 64 | def rval(val: 'bool') -> 'qp.RValue[bool]': 65 | pass 66 | 67 | 68 | @overload 69 | def rval(val: 'qp.RValue[T]') -> 'qp.RValue[T]': 70 | pass 71 | 72 | 73 | @overload 74 | def rval(val: Any, default: Any) -> 'qp.RValue[Any]': 75 | pass 76 | 77 | 78 | def rval(val: Any, default: Any = _raise_on_fail) -> 'qp.RValue[Any]': 79 | """Wraps the given candidate value into a `qp.RValue`, if needed. 80 | 81 | Args: 82 | val: The value that the caller wants as an rvalue. 83 | default: Default result to return for an unrecognized type of value. Defaults to raising an exception. 84 | 85 | Returns: 86 | A `qp.RValue` wrapper around the given candidate value. 87 | """ 88 | if isinstance(val, bool): 89 | return qp.BoolRValue(val) 90 | if isinstance(val, int): 91 | return qp.IntRValue(val) 92 | if isinstance(val, qp.IntBuf): 93 | return qp.IntRValue(int(val)) 94 | if isinstance(val, qp.Qubit): 95 | return qp.QubitRValue(val) 96 | if isinstance(val, qp.Quint): 97 | return qp.QuintRValue(val) 98 | if isinstance(val, qp.RValue): 99 | return val 100 | if default is not _raise_on_fail: 101 | return default 102 | raise NotImplementedError( 103 | "Don't know how to wrap type {} (value {!r}).".format( 104 | type(val), 105 | val)) 106 | -------------------------------------------------------------------------------- /quantumpseudocode/shor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strilanc/quantumpseudocode/da4e86e8a8709bab6ca5f199f0aa8799a6b1248d/quantumpseudocode/shor/__init__.py -------------------------------------------------------------------------------- /quantumpseudocode/shor/measure_pow_mod.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import math 4 | 5 | from quantumpseudocode import * 6 | 7 | 8 | def measure_pow_mod(base: int, exponent: Quint, modulus: int) -> int: 9 | """Measure `base**exponent % modulus`, and nothing else.""" 10 | assert modular_multiplicative_inverse(base, modulus) is not None 11 | 12 | n = ceil_lg2(modulus) # Problem size in bits. 13 | g = ceil_lg2(n) // 3 + 1 # Group size for lookups. 14 | g0 = g 15 | g1 = g 16 | coset_len = n + 2 * ceil_lg2(n) + 10 # Extra bits to suppress deviation. 17 | 18 | # Initialize coset registers to starting state (a=1, b=0). 19 | a = make_coset_register(value=1, length=coset_len, modulus=modulus) 20 | b = make_coset_register(value=0, length=coset_len, modulus=modulus) 21 | 22 | # Perform a *= base**exponent (mod modulus) using b as workspace. 23 | for i in range(0, len(exponent), g0): 24 | exp_index = exponent[i:i+g0] 25 | ks = [pow(base, 2**i * e, modulus) for e in range(2**g0)] 26 | ks_inv = [modular_multiplicative_inverse(k, modulus) for k in ks] 27 | 28 | # Perform b += a * k (mod modulus). 29 | # Maps (x, 0) into (x, x*k). 30 | for j in range(0, coset_len, g1): 31 | mul_index = a[j:j + g1] 32 | table = LookupTable( 33 | [(k * f * 2**j) % modulus for f in range(2**len(mul_index))] 34 | for k in ks 35 | ) 36 | b += table[exp_index, mul_index] 37 | 38 | # Perform a -= b * inv(k) (mod modulus). 39 | # Maps (x, x*k) into (0, x*k). 40 | for j in range(0, coset_len, g1): 41 | mul_index = b[j:j + g1] 42 | table = LookupTable( 43 | [(k_inv * f * 2**j) % modulus for f in range(2**len(mul_index))] 44 | for k_inv in ks_inv 45 | ) 46 | a -= table[exp_index, mul_index] 47 | 48 | # Swap. 49 | # Maps (0, x*k) into (x*k, 0). 50 | a, b = b, a 51 | 52 | result = measure(a) 53 | qfree(a, dirty=True) 54 | qfree(b, dirty=True) 55 | 56 | return result % modulus 57 | 58 | 59 | def make_coset_register(value: int, length: int, modulus: int) -> Quint: 60 | reg = qalloc(len=length, name='coset') 61 | reg ^= value % modulus 62 | 63 | # Add coherent multiple of modulus into reg. 64 | pad_bits = length - modulus.bit_length() 65 | for i in range(pad_bits): 66 | offset = modulus << i 67 | q = qalloc(x_basis=True) 68 | reg += offset & controlled_by(q) 69 | qfree(q, equivalent_expression=reg >= offset) 70 | 71 | return reg 72 | -------------------------------------------------------------------------------- /quantumpseudocode/shor/measure_pow_mod_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import math 4 | 5 | import pytest 6 | 7 | import quantumpseudocode as qp 8 | from .measure_pow_mod import measure_pow_mod, make_coset_register 9 | 10 | 11 | @pytest.mark.parametrize("exp_len,modulus_len,emulate_additions", [ 12 | (3, 5, False), 13 | (6, 12, False), 14 | # (20, 40, True), 15 | # (25, 60, True), 16 | # (30, 15, True), 17 | ]) 18 | def test_integration(exp_len: int, modulus_len: int, emulate_additions: bool): 19 | with qp.Sim(emulate_additions=emulate_additions): 20 | exponent = random.randint(0, 1 << exp_len) 21 | modulus = random.randint(0, 1 << modulus_len) * 2 + 1 22 | base = 0 23 | while math.gcd(base, modulus) != 1 or base >= modulus: 24 | base = random.randint(0, 1 << modulus_len) * 2 + 1 25 | 26 | with qp.hold(exponent) as exp: 27 | actual = measure_pow_mod(base=base, exponent=exp, modulus=modulus) 28 | 29 | expected = pow(base, exponent, modulus) 30 | assert actual == expected 31 | -------------------------------------------------------------------------------- /quantumpseudocode/sim.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import List, Union, Callable, Any, Optional, Tuple, Set, Dict, Iterable 3 | 4 | import quantumpseudocode as qp 5 | import quantumpseudocode.ops.operation 6 | 7 | 8 | def _toggle_targets(lvalue: 'qp.Qureg') -> 'qp.Qureg': 9 | return lvalue 10 | 11 | 12 | class Sim(quantumpseudocode.sink.Sink, quantumpseudocode.ops.operation.ClassicalSimState): 13 | def __init__(self, 14 | enforce_release_at_zero: bool = True, 15 | phase_fixup_bias: Optional[bool] = None, 16 | emulate_additions: bool = False): 17 | super().__init__() 18 | self._int_state: Dict[str, 'qp.IntBuf'] = {} 19 | self.enforce_release_at_zero = enforce_release_at_zero 20 | self.phase_fixup_bias = phase_fixup_bias 21 | self.emulate_additions = emulate_additions 22 | self._phase_degrees = 0 23 | self._anon_alloc_counter = 0 24 | 25 | @property 26 | def phase_degrees(self): 27 | return self._phase_degrees 28 | 29 | @phase_degrees.setter 30 | def phase_degrees(self, new_value): 31 | self._phase_degrees = new_value % 360 32 | 33 | def snapshot(self): 34 | return dict(self._int_state) 35 | 36 | def _read_qubit(self, qubit: 'qp.Qubit') -> bool: 37 | return self._int_state[qubit.name][qubit.index or 0] 38 | 39 | def _write_qubit(self, qubit: 'qp.Qubit', new_val: bool): 40 | self._int_state[qubit.name][qubit.index or 0] = new_val 41 | 42 | def quint_buf(self, quint: 'qp.Quint') -> qp.IntBuf: 43 | if len(quint) == 0: 44 | return qp.IntBuf.raw(val=0, length=0) 45 | if isinstance(quint.qureg, qp.NamedQureg): 46 | return self._int_state[quint.qureg.name] 47 | fused = _fuse(quint.qureg) 48 | return qp.IntBuf(qp.RawConcatBuffer.balanced_concat([ 49 | self._int_state[name][rng]._buf for name, rng in fused 50 | ])) 51 | 52 | def resolve_location(self, loc: Any, allow_mutate: bool = True): 53 | resolver = getattr(loc, 'resolve', None) 54 | if resolver is not None: 55 | return resolver(self, allow_mutate) 56 | if isinstance(loc, (int, bool)): 57 | return loc 58 | raise NotImplementedError( 59 | "Don't know how to resolve type {!r}. Value: {!r}".format(type(loc), loc)) 60 | 61 | def randomize_location(self, loc: Union[qp.Quint, qp.Qubit, qp.Qureg]): 62 | if isinstance(loc, qp.Qubit): 63 | self._write_qubit(loc, random.random() < 0.5) 64 | elif isinstance(loc, qp.Qureg): 65 | for q in loc: 66 | self._write_qubit(q, random.random() < 0.5) 67 | elif isinstance(loc, qp.Quint): 68 | for q in loc: 69 | self._write_qubit(q, random.random() < 0.5) 70 | elif isinstance(loc, qp.ControlledRValue): 71 | if self.resolve_location(loc.controls): 72 | self.randomize_location(loc.rvalue) 73 | else: 74 | raise NotImplementedError( 75 | "Unrecognized type for randomize_location {!r}".format(loc)) 76 | 77 | def measurement_based_uncomputation_result_chooser(self) -> Callable[[], bool]: 78 | if self.phase_fixup_bias is not None: 79 | return lambda: self.phase_fixup_bias 80 | return lambda: random.random() < 0.5 81 | 82 | def do_allocate(self, args: 'qp.AllocArgs') -> 'qp.Qureg': 83 | if args.qureg_name is None: 84 | name = f'_anon_{self._anon_alloc_counter}' 85 | self._anon_alloc_counter += 1 86 | else: 87 | name = args.qureg_name 88 | 89 | if name in self._int_state: 90 | k = 1 91 | while True: 92 | candidate = f'{name}_{k}' 93 | if candidate not in self._int_state: 94 | break 95 | k += 1 96 | name = candidate 97 | result = qp.NamedQureg(name=name, length=args.qureg_length) 98 | self._int_state[result.name] = qp.IntBuf.raw( 99 | val=random.randint(0, (1 << args.qureg_length) - 1) if args.x_basis else 0, 100 | length=args.qureg_length) 101 | return result 102 | 103 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 104 | pass 105 | 106 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 107 | if self.enforce_release_at_zero and not op.dirty: 108 | v = self.do_measure(op.qureg, reset=False) 109 | if v: 110 | raise ValueError(f'Failed to uncompute {op.qureg!r} before release. It had value {v}.') 111 | 112 | assert isinstance(op.qureg, qp.NamedQureg) 113 | assert op.qureg.name in self._int_state 114 | del self._int_state[op.qureg.name] 115 | 116 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 117 | reg = self.quint_buf(qp.Quint(qureg)) 118 | result = int(reg) 119 | if reset: 120 | reg[:] = 0 121 | return result 122 | 123 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 124 | pass 125 | 126 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 127 | captured_phase_degrees = self.phase_degrees 128 | z_result = self.do_measure(qureg, reset=True) 129 | 130 | # Simulate X basis measurements. 131 | chooser = self.measurement_based_uncomputation_result_chooser() 132 | x_result = 0 133 | for i in range(len(qureg))[::-1]: 134 | x_result <<= 1 135 | if chooser(): 136 | x_result |= 1 137 | if qp.popcnt(x_result & z_result) & 1: 138 | self.phase_degrees += 180 139 | 140 | return qp.StartMeasurementBasedUncomputationResult(measurement=x_result, context=captured_phase_degrees) 141 | 142 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 143 | pass 144 | 145 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 146 | if self.phase_degrees != start.context: 147 | raise AssertionError('Failed to uncompute. Measurement based uncomputation failed to fix phase flips.') 148 | 149 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 150 | if self.resolve_location(controls, allow_mutate=False): 151 | self.phase_degrees += 180 152 | 153 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 154 | assert set(targets).isdisjoint(controls.qubits) 155 | if controls.bit and all(self._read_qubit(q) for q in controls.qubits): 156 | for t in targets: 157 | self._write_qubit(t, not self._read_qubit(t)) 158 | 159 | 160 | def _fuse(qubits: Iterable[qp.Qubit]) -> List[Tuple[str, slice]]: 161 | result: List[Tuple[str, slice]] = [] 162 | cur_name = None 163 | cur_start = None 164 | cur_end = None 165 | 166 | def flush(): 167 | if cur_name is not None and cur_end > cur_start: 168 | result.append((cur_name, slice(cur_start, cur_end))) 169 | 170 | for q in qubits: 171 | if q.name == cur_name and q.index == cur_end: 172 | cur_end += 1 173 | else: 174 | flush() 175 | cur_name = q.name 176 | cur_start = q.index or 0 177 | cur_end = cur_start + 1 178 | flush() 179 | return result 180 | -------------------------------------------------------------------------------- /quantumpseudocode/sim_test.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def test_sim(): 5 | v1 = 15 6 | v2 = 235 7 | offset = 4 8 | bits = 10 9 | with qp.Sim(): 10 | with qp.hold(val=v1, name='a') as a: 11 | with qp.qalloc(len=bits, name='out') as out: 12 | out += a * v2 13 | out += offset 14 | result = qp.measure(out, reset=True) 15 | assert result == (v1*v2 + offset) & ((1 << bits) - 1) 16 | 17 | 18 | def test_count(): 19 | v1 = 15 20 | v2 = 235 21 | offset = 4 22 | bits = 100 23 | with qp.Sim(): 24 | with qp.CountNots() as counts: 25 | with qp.hold(val=v1, name='a') as a: 26 | with qp.qalloc(len=bits, name='out') as out: 27 | out += a * v2 28 | out += offset 29 | _ = qp.measure(out, reset=True) 30 | assert len(counts.keys()) == 3 31 | assert counts[0] > 0 32 | assert counts[1] > 0 33 | assert 0 < counts[2] <= 1000 34 | -------------------------------------------------------------------------------- /quantumpseudocode/sink.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import dataclasses 3 | import random 4 | from typing import List, Optional, ContextManager, cast, Tuple, Union, Any 5 | 6 | import quantumpseudocode as qp 7 | 8 | 9 | def capture(out: 'Optional[List[qp.Operation]]' = None) -> ContextManager[List['qp.Operation']]: 10 | return cast(ContextManager, CaptureLens([] if out is None else out)) 11 | 12 | 13 | class EmptyManager: 14 | def __init__(self, value=None): 15 | self.value = value 16 | 17 | def __enter__(self): 18 | return self.value 19 | 20 | def __exit__(self, exc_type, exc_val, exc_tb): 21 | pass 22 | 23 | 24 | @dataclasses.dataclass 25 | class StartMeasurementBasedUncomputationResult: 26 | measurement: int 27 | context: Any 28 | 29 | 30 | class Sink(metaclass=abc.ABCMeta): 31 | def __init__(self): 32 | self.used = False 33 | 34 | def do_allocate(self, args: 'qp.AllocArgs') -> 'qp.Qureg': 35 | result = qp.NamedQureg(args.qureg_name or '', length=args.qureg_length) 36 | self.did_allocate(args, result) 37 | return result 38 | 39 | @abc.abstractmethod 40 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 41 | pass 42 | 43 | @abc.abstractmethod 44 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 45 | pass 46 | 47 | @abc.abstractmethod 48 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 49 | pass 50 | 51 | @abc.abstractmethod 52 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 53 | pass 54 | 55 | @abc.abstractmethod 56 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 57 | pass 58 | 59 | @abc.abstractmethod 60 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 61 | pass 62 | 63 | @abc.abstractmethod 64 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 65 | pass 66 | 67 | @abc.abstractmethod 68 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 69 | pass 70 | 71 | @abc.abstractmethod 72 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 73 | pass 74 | 75 | def _val(self): 76 | return self 77 | 78 | def _succeeded(self): 79 | pass 80 | 81 | def __enter__(self): 82 | assert not self.used 83 | self.used = True 84 | global_sink.sinks.append(self) 85 | return self._val() 86 | 87 | def __exit__(self, exc_type, exc_val, exc_tb): 88 | assert global_sink.sinks[-1] is self 89 | global_sink.sinks.pop() 90 | if exc_type is None: 91 | self._succeeded() 92 | 93 | 94 | class RandomSim(Sink): 95 | def __init__(self, measure_bias: float): 96 | super().__init__() 97 | self.measure_bias = measure_bias 98 | 99 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 100 | pass 101 | 102 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 103 | pass 104 | 105 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 106 | pass 107 | 108 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 109 | pass 110 | 111 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 112 | bits = tuple(random.random() < self.measure_bias for _ in range(len(qureg))) 113 | result = qp.little_endian_int(bits) 114 | self.did_measure(qureg, reset, result) 115 | return result 116 | 117 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 118 | pass 119 | 120 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 121 | return StartMeasurementBasedUncomputationResult(self.do_measure(qureg, False), None) 122 | 123 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 124 | pass 125 | 126 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 127 | pass 128 | 129 | 130 | class CaptureLens(Sink): 131 | def __init__(self, out: List[Tuple[str, Any]]): 132 | super().__init__() 133 | self.out = out 134 | 135 | def __enter__(self): 136 | super().__enter__() 137 | return self.out 138 | 139 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 140 | self.out.append(('alloc', (args, qureg))) 141 | 142 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 143 | self.out.append(('release', op)) 144 | 145 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 146 | self.out.append(('phase_flip', controls)) 147 | 148 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 149 | self.out.append(('toggle', (targets, controls))) 150 | 151 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 152 | raise NotImplementedError() 153 | 154 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 155 | self.out.append(('measure', (qureg, reset, result))) 156 | 157 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 158 | raise NotImplementedError() 159 | 160 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 161 | self.out.append(('start_measurement_based_uncomputation', (qureg, result))) 162 | 163 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 164 | self.out.append(('end_measurement_based_uncomputation', (qureg, start))) 165 | 166 | 167 | class _GlobalSink(Sink): 168 | def __init__(self): 169 | super().__init__() 170 | self.sinks: List['qp.Sink'] = [] 171 | 172 | def do_allocate(self, args: 'qp.AllocArgs') -> 'qp.Qureg': 173 | result = self.sinks[0].do_allocate(args) 174 | for sink in self.sinks[1:]: 175 | sink.did_allocate(args, result) 176 | return result 177 | 178 | def did_allocate(self, args: 'qp.AllocArgs', qureg: 'qp.Qureg'): 179 | for sink in self.sinks: 180 | sink.did_allocate(args, qureg) 181 | 182 | def do_release(self, op: 'qp.ReleaseQuregOperation'): 183 | for sink in self.sinks: 184 | sink.do_release(op) 185 | 186 | def do_phase_flip(self, controls: 'qp.QubitIntersection'): 187 | for sink in self.sinks: 188 | sink.do_phase_flip(controls) 189 | 190 | def do_toggle(self, targets: 'qp.Qureg', controls: 'qp.QubitIntersection'): 191 | for sink in self.sinks: 192 | sink.do_toggle(targets, controls) 193 | 194 | def do_measure(self, qureg: 'qp.Qureg', reset: bool) -> int: 195 | result = self.sinks[0].do_measure(qureg, reset) 196 | for sink in self.sinks[1:]: 197 | sink.did_measure(qureg, reset, result) 198 | return result 199 | 200 | def did_measure(self, qureg: 'qp.Qureg', reset: bool, result: int): 201 | for sink in self.sinks: 202 | sink.did_measure(qureg, reset, result) 203 | 204 | def do_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg') -> 'qp.StartMeasurementBasedUncomputationResult': 205 | result = self.sinks[0].do_start_measurement_based_uncomputation(qureg) 206 | for sink in self.sinks[1:]: 207 | sink.did_start_measurement_based_uncomputation(qureg, result) 208 | return result 209 | 210 | def did_start_measurement_based_uncomputation(self, qureg: 'qp.Qureg', result: 'qp.StartMeasurementBasedUncomputationResult'): 211 | for sink in self.sinks: 212 | sink.did_start_measurement_based_uncomputation(qureg, result) 213 | 214 | def do_end_measurement_based_uncomputation(self, qureg: 'qp.Qureg', start: 'qp.StartMeasurementBasedUncomputationResult'): 215 | for sink in self.sinks: 216 | sink.do_end_measurement_based_uncomputation(qureg, start) 217 | 218 | 219 | global_sink = _GlobalSink() 220 | -------------------------------------------------------------------------------- /quantumpseudocode/testing/__init__.py: -------------------------------------------------------------------------------- 1 | from .sim_call import ( 2 | ModInt, 3 | sim_call, 4 | ) 5 | 6 | from .verify_semi_quantum import ( 7 | assert_semi_quantum_func_is_consistent, 8 | ) 9 | -------------------------------------------------------------------------------- /quantumpseudocode/testing/sim_call.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | import quantumpseudocode as qp 4 | 5 | 6 | class ModInt: 7 | def __init__(self, val: int, modulus: int): 8 | self.val = val 9 | self.modulus = modulus 10 | 11 | 12 | def sim_call(func: Callable, 13 | *args, **kwargs) -> 'qp.ArgsAndKwargs': 14 | 15 | def qalloc_as_needed(a: qp.ArgParameter): 16 | if a.parameter_type is None: 17 | raise ValueError('Function arguments must be annotated, so that ' 18 | 'quantum values can be allocated if needed.') 19 | 20 | if a.parameter_type is qp.Quint: 21 | if isinstance(a.arg, qp.IntBuf): 22 | n = len(a.arg) 23 | elif isinstance(a.arg, int): 24 | n = a.arg.bit_length() 25 | else: 26 | raise ValueError('Unsupported Quint input: {}'.format(a)) 27 | result = qp.qalloc(len=n, name=a.parameter.name) 28 | result.init(a.arg) 29 | return result 30 | 31 | if a.parameter_type is qp.QuintMod: 32 | assert isinstance(a.arg, ModInt) 33 | result = qp.qalloc(modulus=a.arg.modulus, 34 | name=a.parameter.name) 35 | result.init(a.arg.val) 36 | return result 37 | 38 | if a.parameter_type is qp.Qubit: 39 | result = qp.qalloc(name=a.parameter.name) 40 | result.init(a.arg) 41 | return result 42 | 43 | return a.arg 44 | 45 | def qfree_as_needed(a: Any): 46 | if isinstance(a, (qp.Quint, qp.Qureg, qp.Qubit, qp.QuintMod)): 47 | result = qp.measure(a, reset=True) 48 | qp.qfree(a) 49 | return result 50 | return a 51 | 52 | matched = qp.ArgsAndKwargs(args, kwargs).match_parameters(func) 53 | with qp.Sim(): 54 | allocated = matched.map(qalloc_as_needed) 55 | allocated.pass_into(func) 56 | return allocated.map(qfree_as_needed) 57 | -------------------------------------------------------------------------------- /quantumpseudocode/testing/verify_semi_quantum.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import random 3 | from typing import Any, Callable, Iterable, Sequence, get_type_hints, Union, Dict, TypeVar, Generic, List 4 | 5 | import quantumpseudocode as qp 6 | 7 | 8 | TSample = TypeVar('TSample') 9 | 10 | 11 | def assert_semi_quantum_func_is_consistent( 12 | func: Callable, 13 | fuzz_space: Dict[str, Any] = None, 14 | fuzz_count: int = 0, 15 | fixed: Sequence[Dict[str, Any]] = ()): 16 | __tracebackhide__ = True 17 | 18 | classical = getattr(func, 'classical', None) 19 | assert classical is not None, f'Function {func} does not specify a classical= decorator argument.' 20 | assert fuzz_count or fixed 21 | 22 | quantum_has_control = 'control' in get_type_hints(func) 23 | for kwargs in fixed: 24 | _assert_semi_quantum_func_is_consistent(func, quantum_has_control, classical, kwargs) 25 | for _ in range(fuzz_count): 26 | _assert_semi_quantum_func_is_consistent(func, quantum_has_control, classical, _sample(fuzz_space)) 27 | 28 | 29 | def _assert_semi_quantum_func_is_consistent( 30 | quantum_func: Callable, 31 | quantum_has_control: bool, 32 | classical_func: Callable, 33 | kwargs: Dict[str, Any]): 34 | __tracebackhide__ = True 35 | 36 | if 'control' not in kwargs and quantum_has_control: 37 | for control in [False, True]: 38 | _assert_semi_quantum_func_is_consistent(quantum_func, 39 | quantum_has_control, 40 | classical_func, 41 | {**kwargs, 'control': control}) 42 | return 43 | 44 | classical_result = _apply_classical(classical_func, kwargs) 45 | quantum_result = _apply_quantum(quantum_func, kwargs) 46 | assert classical_result == quantum_result, '\n'.join([ 47 | 'Classical function disagreed with quantum function.' 48 | '', 49 | 'Quantum function: {}'.format(quantum_func), 50 | 'Classical function: {}'.format(classical_func), 51 | '', 52 | 'Input: {!r}'.format(kwargs), 53 | '', 54 | 'Quantum output: {!r}'.format(quantum_result), 55 | 'Classical output: {!r}'.format(classical_result), 56 | ]) 57 | 58 | 59 | def _apply_quantum(func: Callable, kwargs: Dict[str, Any]) -> Dict[str, Any]: 60 | with qp.Sim() as sim: 61 | type_hints = get_type_hints(func) 62 | mapped = {} 63 | for k, v in kwargs.items(): 64 | if type_hints[k] == qp.Quint: 65 | assert isinstance(v, qp.IntBuf), 'Expected type qp.IntBuf for argument "{}"'.format(k) 66 | mapped[k] = qp.qalloc(len=len(v), name=k) 67 | mapped[k] ^= int(v) 68 | elif type_hints[k] == qp.Qubit: 69 | assert isinstance(v, qp.IntBuf) and len(v) == 1, 'Expected length 1 qp.IntBuf for argument "{}"'.format( 70 | k) 71 | mapped[k] = qp.qalloc(name=k) 72 | mapped[k] ^= int(v) 73 | else: 74 | mapped[k] = v 75 | 76 | func(**mapped) 77 | 78 | result = {} 79 | for k, v in kwargs.items(): 80 | if type_hints[k] in [qp.Quint, qp.Qubit]: 81 | result[k] = qp.measure(mapped[k], reset=True) 82 | result['sim_state.phase_degrees'] = sim.phase_degrees 83 | return result 84 | 85 | 86 | def _apply_classical(func: Callable, kwargs: Dict[str, Any]) -> Dict[str, Any]: 87 | kwargs = {k: v.copy() if hasattr(v, 'copy') else v for k, v in kwargs.items()} 88 | 89 | type_hints = get_type_hints(func) 90 | if 'control' in kwargs and 'control' not in type_hints: 91 | run = kwargs['control'] 92 | del kwargs['control'] 93 | else: 94 | run = True 95 | 96 | if run: 97 | if 'sim_state' in type_hints: 98 | kwargs['sim_state'] = qp.Sim() 99 | func(**kwargs) 100 | 101 | result = {'sim_state.phase_degrees': 0} 102 | for k, v in kwargs.items(): 103 | if isinstance(v, qp.IntBuf): 104 | result[k] = int(v) 105 | elif isinstance(v, qp.Sim): 106 | result['sim_state.phase_degrees'] = v.phase_degrees 107 | return result 108 | 109 | 110 | def _sample(kwargs: Dict[str, Any]) -> Dict[str, Any]: 111 | result = {} 112 | for k, space in kwargs.items(): 113 | if isinstance(space, Callable): 114 | v = space(**{k: result[k] for k in inspect.signature(space).parameters}) 115 | elif isinstance(space, Sequence): 116 | v = random.choice(space) 117 | else: 118 | v = space 119 | result[k] = v 120 | return result 121 | -------------------------------------------------------------------------------- /quantumpseudocode/testing/verify_semi_quantum_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Any, Callable, Iterable, Sequence, get_type_hints, Union, Dict, TypeVar, Generic, List 3 | 4 | import pytest 5 | 6 | import quantumpseudocode as qp 7 | 8 | 9 | TSample = TypeVar('TSample') 10 | 11 | 12 | def test_correct(): 13 | def classical_cnot_pairs(zs: int, xs: qp.IntBuf): 14 | parity = 0 15 | while zs: 16 | zs &= zs - 1 17 | parity += 1 18 | if parity & 1: 19 | xs ^= -1 20 | 21 | @qp.semi_quantum(classical=classical_cnot_pairs) 22 | def quantum_cnot_pairs(zs: qp.Quint.Borrowed, xs: qp.Quint): 23 | for z in zs: 24 | for x in xs: 25 | x ^= z 26 | 27 | qp.testing.assert_semi_quantum_func_is_consistent( 28 | quantum_cnot_pairs, 29 | fixed=[ 30 | {'xs': qp.IntBuf.raw(length=0, val=0), 'zs': 0} 31 | ], 32 | fuzz_count=100, 33 | fuzz_space={ 34 | 'xs': lambda: qp.IntBuf.random(range(0, 6)), 35 | 'zs': lambda: random.randint(0, 63) 36 | }) 37 | 38 | 39 | def test_bad_uncompute(): 40 | def cf(c: bool, t: qp.IntBuf): 41 | t ^= c 42 | c ^= int(t) 43 | 44 | @qp.semi_quantum(classical=cf) 45 | def qf(c: qp.Qubit.Borrowed, t: qp.Qubit): 46 | t ^= c 47 | c ^= t 48 | 49 | with pytest.raises(AssertionError, match='Failed to uncompute'): 50 | qp.testing.assert_semi_quantum_func_is_consistent( 51 | qf, 52 | fuzz_space={'t': lambda: qp.IntBuf.random(1), 'c': [False, True]}, 53 | fuzz_count=100) 54 | 55 | 56 | def test_bad_control(): 57 | def classical_flip(t: qp.IntBuf): 58 | t ^= 1 59 | 60 | @qp.semi_quantum(classical=classical_flip) 61 | def quantum_cnot_pairs(control: qp.Qubit.Control, t: qp.Qubit): 62 | t ^= 1 63 | 64 | # Catch issue when control is allowed to be False. 65 | with pytest.raises(AssertionError, match='disagreed'): 66 | qp.testing.assert_semi_quantum_func_is_consistent( 67 | quantum_cnot_pairs, 68 | fixed=[ 69 | {'t': qp.IntBuf.raw(length=1, val=0), 'control': False} 70 | ]) 71 | with pytest.raises(AssertionError, match='disagreed'): 72 | qp.testing.assert_semi_quantum_func_is_consistent( 73 | quantum_cnot_pairs, 74 | fixed=[ 75 | {'t': qp.IntBuf.raw(length=1, val=0)} 76 | ]) 77 | with pytest.raises(AssertionError, match='disagreed'): 78 | qp.testing.assert_semi_quantum_func_is_consistent( 79 | quantum_cnot_pairs, 80 | fuzz_count=100, 81 | fuzz_space={ 82 | 't': qp.IntBuf.raw(length=1, val=0) 83 | }) 84 | with pytest.raises(AssertionError, match='disagreed'): 85 | qp.testing.assert_semi_quantum_func_is_consistent( 86 | quantum_cnot_pairs, 87 | fuzz_count=100, 88 | fuzz_space={ 89 | 't': qp.IntBuf.raw(length=1, val=0), 90 | 'control': [False, True] 91 | }) 92 | with pytest.raises(AssertionError, match='disagreed'): 93 | qp.testing.assert_semi_quantum_func_is_consistent( 94 | quantum_cnot_pairs, 95 | fuzz_count=100, 96 | fuzz_space={ 97 | 't': qp.IntBuf.raw(length=1, val=0), 98 | 'control': lambda: random.randint(0, 1) 99 | }) 100 | with pytest.raises(AssertionError, match='disagreed'): 101 | qp.testing.assert_semi_quantum_func_is_consistent( 102 | quantum_cnot_pairs, 103 | fuzz_count=100, 104 | fuzz_space={ 105 | 't': qp.IntBuf.raw(length=1, val=0), 106 | 'control': [False] 107 | }) 108 | with pytest.raises(AssertionError, match='disagreed'): 109 | qp.testing.assert_semi_quantum_func_is_consistent( 110 | quantum_cnot_pairs, 111 | fuzz_count=100, 112 | fuzz_space={ 113 | 't': qp.IntBuf.raw(length=1, val=0), 114 | 'control': False 115 | }) 116 | 117 | # Fail to catch when control is forced to be true. 118 | qp.testing.assert_semi_quantum_func_is_consistent( 119 | quantum_cnot_pairs, 120 | fixed=[ 121 | {'t': qp.IntBuf.raw(length=1, val=0), 'control': True} 122 | ]) 123 | qp.testing.assert_semi_quantum_func_is_consistent( 124 | quantum_cnot_pairs, 125 | fuzz_count=20, 126 | fuzz_space={ 127 | 't': qp.IntBuf.raw(length=1, val=0), 128 | 'control': [True] 129 | }) 130 | qp.testing.assert_semi_quantum_func_is_consistent( 131 | quantum_cnot_pairs, 132 | fuzz_count=20, 133 | fuzz_space={ 134 | 't': qp.IntBuf.raw(length=1, val=0), 135 | 'control': True 136 | }) 137 | qp.testing.assert_semi_quantum_func_is_consistent( 138 | quantum_cnot_pairs, 139 | fuzz_count=20, 140 | fuzz_space={ 141 | 't': qp.IntBuf.raw(length=1, val=0), 142 | 'control': lambda: 1 143 | }) 144 | 145 | 146 | def test_phase_match(): 147 | def cf(sim_state: qp.ClassicalSimState): 148 | sim_state.phase_degrees += 180 149 | 150 | @qp.semi_quantum(classical=cf) 151 | def qf(): 152 | qp.phase_flip() 153 | 154 | qp.testing.assert_semi_quantum_func_is_consistent( 155 | qf, 156 | fixed=[{}]) 157 | 158 | 159 | def test_phase_mismatch(): 160 | def cf(sim_state: qp.ClassicalSimState): 161 | sim_state.phase_degrees += 90 162 | 163 | @qp.semi_quantum(classical=cf) 164 | def qf(): 165 | qp.phase_flip() 166 | 167 | with pytest.raises(AssertionError, match='disagreed'): 168 | qp.testing.assert_semi_quantum_func_is_consistent( 169 | qf, 170 | fixed=[{}]) 171 | -------------------------------------------------------------------------------- /quantumpseudocode/util.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import cirq 4 | import inspect 5 | from typing import Callable, TypeVar, Generic, List, Dict, Iterable, Any, get_type_hints, Optional, Tuple, Sequence 6 | import quantumpseudocode as qp 7 | 8 | T = TypeVar('T') 9 | T2 = TypeVar('T2') 10 | R = TypeVar('R') 11 | 12 | 13 | def ceil_lg2(x: int) -> int: 14 | if x <= 1: 15 | return 0 16 | return (x - 1).bit_length() 17 | 18 | 19 | def ceil_power_of_two(x: int) -> int: 20 | return 1 << ceil_lg2(x) 21 | 22 | 23 | def floor_power_of_two(x: int) -> int: 24 | return 1 << floor_lg2(x) 25 | 26 | 27 | def floor_lg2(x: int) -> int: 28 | assert x >= 1 29 | return x.bit_length() - 1 30 | 31 | 32 | class ArgParameter: 33 | def __init__(self, 34 | arg: Any, 35 | parameter: inspect.Parameter, 36 | parameter_type: Optional[type]): 37 | self.arg = arg 38 | self.parameter = parameter 39 | self.parameter_type = parameter_type 40 | 41 | def __repr__(self): 42 | return 'qp.ArgParameter({!r}, {!r}, {!r})'.format( 43 | self.arg, 44 | self.parameter, 45 | self.parameter_type) 46 | 47 | 48 | @cirq.value_equality(unhashable=True) 49 | class ArgsAndKwargs(Generic[T]): 50 | def __init__(self, args: List[T], kwargs: Dict[str, T]): 51 | self.args = args 52 | self.kwargs = kwargs 53 | 54 | def resolve(self, sim_state: 'qp.ClassicalSimState', allow_mutate: bool): 55 | return self.map(lambda e: sim_state.resolve_location(e, allow_mutate)) 56 | 57 | def _value_equality_values_(self): 58 | return self.args, self.kwargs 59 | 60 | def pass_into(self, func: Callable[[Any], R]) -> R: 61 | return func(*self.args, **self.kwargs) 62 | 63 | def match_parameters(self, 64 | func: Callable, 65 | skip: int = 0) -> 'ArgsAndKwargs[ArgParameter]': 66 | sig = inspect.signature(func) 67 | type_hints = get_type_hints(func) 68 | result_args = [] 69 | 70 | for p, a in zip(list(sig.parameters.values())[skip:], self.args): 71 | assert p.name not in self.kwargs 72 | assert p.kind in [inspect.Parameter.POSITIONAL_ONLY, 73 | inspect.Parameter.POSITIONAL_OR_KEYWORD] 74 | t = type_hints.get(p.name, None) 75 | result_args.append(ArgParameter(a, p, t)) 76 | 77 | result_kwargs = {} 78 | for k, a in self.kwargs.items(): 79 | p = sig.parameters[k] 80 | assert p.kind in [inspect.Parameter.KEYWORD_ONLY, 81 | inspect.Parameter.POSITIONAL_OR_KEYWORD] 82 | t = type_hints.get(k, None) 83 | result_kwargs[k] = ArgParameter(a, p, t) 84 | 85 | if len(result_args) + len(result_kwargs) + skip != len(sig.parameters): 86 | raise AssertionError( 87 | 'Unmatched arguments.\n' 88 | 'Args: {}\nKwargs: {}\nSkip: {!r}\nSignature: {}'.format( 89 | result_args, 90 | sorted(result_kwargs.keys()), 91 | skip, 92 | sig.parameters)) 93 | assert len(result_args) + len(result_kwargs) + skip == len(sig.parameters) 94 | return ArgsAndKwargs(result_args, result_kwargs) 95 | 96 | def map(self, func: Callable[[T], R]) -> 'ArgsAndKwargs[R]': 97 | return ArgsAndKwargs( 98 | [func(e) for e in self.args], 99 | {k: func(v) for k, v in self.kwargs.items()}) 100 | 101 | def key_map(self, func: Callable[[Any, T], R]) -> 'ArgsAndKwargs[R]': 102 | return ArgsAndKwargs( 103 | [func(i, e) for i, e in enumerate(self.args)], 104 | {k: func(k, v) for k, v in self.kwargs.items()}) 105 | 106 | def zip_map(self, 107 | other: 'ArgsAndKwargs[T2]', 108 | func: Callable[[T, T2], R] 109 | ) -> 'ArgsAndKwargs[R]': 110 | assert len(self.args) == len(other.args) 111 | assert self.kwargs.keys() == other.kwargs.keys() 112 | return ArgsAndKwargs( 113 | [ 114 | func(e1, e2) 115 | for e1, e2 in zip(self.args, other.args) 116 | ], 117 | { 118 | k: func(self.kwargs[k], other.kwargs[k]) 119 | for k in self.kwargs.keys() 120 | }) 121 | 122 | def __str__(self): 123 | parts = [] 124 | for arg in self.args: 125 | parts.append(str(arg)) 126 | for k, v in self.kwargs.items(): 127 | parts.append('{}={}'.format(k, v)) 128 | return '({})'.format(', '.join(parts)) 129 | 130 | def repr_args(self): 131 | parts = [] 132 | for arg in self.args: 133 | parts.append(repr(arg)) 134 | for k, v in self.kwargs.items(): 135 | parts.append('{}={!r}'.format(k, v)) 136 | return ', '.join(parts) 137 | 138 | def __repr__(self): 139 | return 'qp.ArgsAndKwargs({!r}, {!r})'.format( 140 | self.args, 141 | self.kwargs) 142 | 143 | 144 | class MultiWith: 145 | def __init__(self, items: Iterable[Any]): 146 | self.items = tuple(items) 147 | 148 | def __enter__(self): 149 | return tuple(item.__enter__() for item in self.items) 150 | 151 | def __exit__(self, exc_type, exc_val, exc_tb): 152 | for item in self.items: 153 | item.__exit__(exc_type, exc_val, exc_tb) 154 | 155 | 156 | def leading_zero_bit_count(v: int) -> Optional[int]: 157 | """Returns the largest integer k such that v is divisible by 2**k.""" 158 | if v == 0: 159 | return None 160 | return (v ^ (v - 1)).bit_length() - 1 161 | 162 | 163 | def _extended_gcd(a: int, b: int) -> Tuple[int, int, int]: 164 | x0, x1, y0, y1 = 0, 1, 1, 0 165 | while a != 0: 166 | q, b, a = b // a, a, b % a 167 | y0, y1 = y1, y0 - q * y1 168 | x0, x1 = x1, x0 - q * x1 169 | return b, x0, y0 170 | 171 | 172 | def modular_multiplicative_inverse(a: int, modulus: int) -> Optional[int]: 173 | g, x, y = _extended_gcd(a % modulus, modulus) 174 | if g != 1: 175 | return None 176 | return x % modulus 177 | 178 | 179 | def popcnt(x: int) -> int: 180 | assert x >= 0 181 | t = 0 182 | while x: 183 | x &= x - 1 184 | t += 1 185 | return t 186 | 187 | 188 | def little_endian_int(bits: Sequence[bool]) -> int: 189 | t = 0 190 | for b in reversed(bits): 191 | t <<= 1 192 | if b: 193 | t |= 1 194 | return t 195 | 196 | 197 | def little_endian_bits(val: int, length: int) -> Tuple[bool, ...]: 198 | return tuple(bool(val & (1 << k)) for k in range(length)) 199 | 200 | 201 | def ccz_count(record: Iterable[Tuple[str, Any]]) -> int: 202 | n = 0 203 | for kind, args in record: 204 | if kind == 'toggle': 205 | target, controls = args 206 | assert len(controls.qubits) <= 2 207 | if len(controls.qubits) == 2: 208 | n += 1 209 | elif kind == 'phase_flip': 210 | controls: 'qp.QubitIntersection' = args 211 | assert len(controls.qubits) <= 3 212 | if len(controls.qubits) == 3: 213 | n += 1 214 | return n 215 | -------------------------------------------------------------------------------- /quantumpseudocode/util_test.py: -------------------------------------------------------------------------------- 1 | import quantumpseudocode as qp 2 | 3 | 4 | def test_ceil_lg2(): 5 | f = qp.ceil_lg2 6 | assert f(0) == 0 7 | assert f(1) == 0 8 | assert f(2) == 1 9 | assert f(3) == 2 10 | assert f(4) == 2 11 | assert f(5) == 3 12 | assert f(6) == 3 13 | assert f(7) == 3 14 | assert f(8) == 3 15 | assert f(9) == 4 16 | assert f((1 << 100) - 1) == 100 17 | assert f((1 << 100)) == 100 18 | assert f((1 << 100) + 1) == 101 19 | 20 | 21 | def test_ceil_power_of_two(): 22 | f = qp.ceil_power_of_two 23 | assert f(0) == 1 24 | assert f(1) == 1 25 | assert f(2) == 2 26 | assert f(3) == 4 27 | assert f(4) == 4 28 | assert f(5) == 8 29 | assert f(6) == 8 30 | assert f(7) == 8 31 | assert f(8) == 8 32 | assert f(9) == 16 33 | assert f((1 << 100) - 1) == 1 << 100 34 | assert f((1 << 100)) == 1 << 100 35 | assert f((1 << 100) + 1) == 1 << 101 36 | 37 | 38 | def test_floor_power_of_two(): 39 | f = qp.floor_power_of_two 40 | assert f(1) == 1 41 | assert f(2) == 2 42 | assert f(3) == 2 43 | assert f(4) == 4 44 | assert f(5) == 4 45 | assert f(6) == 4 46 | assert f(7) == 4 47 | assert f(8) == 8 48 | assert f(9) == 8 49 | assert f((1 << 100) - 1) == 1 << 99 50 | assert f((1 << 100)) == 1 << 100 51 | assert f((1 << 100) + 1) == 1 << 100 52 | 53 | 54 | def test_little_endian_int(): 55 | f = qp.little_endian_int 56 | assert f([]) == 0 57 | assert f([False]) == 0 58 | assert f([True]) == 1 59 | assert f([False, False]) == 0 60 | assert f([True, False]) == 1 61 | assert f([False, True]) == 2 62 | assert f([True, True]) == 3 63 | 64 | 65 | def test_little_endian_bits(): 66 | f = qp.little_endian_bits 67 | assert f(0, 4) == (False, False, False, False) 68 | assert f(3, 4) == (True, True, False, False) 69 | assert f(5, 4) == (True, False, True, False) 70 | assert f(5, 6) == (True, False, True, False, False, False) 71 | 72 | 73 | def test_floor_lg2(): 74 | f = qp.floor_lg2 75 | assert f(1) == 0 76 | assert f(2) == 1 77 | assert f(3) == 1 78 | assert f(4) == 2 79 | assert f(5) == 2 80 | assert f(6) == 2 81 | assert f(7) == 2 82 | assert f(8) == 3 83 | assert f(9) == 3 84 | assert f((1 << 100) - 1) == 99 85 | assert f((1 << 100)) == 100 86 | assert f((1 << 100) + 1) == 100 87 | 88 | 89 | def test_leading_zero_bit_count(): 90 | f = qp.leading_zero_bit_count 91 | assert f(-3) == 0 92 | assert f(-2) == 1 93 | assert f(-1) == 0 94 | assert f(0) is None 95 | assert f(1) == 0 96 | assert f(2) == 1 97 | assert f(3) == 0 98 | assert f(4) == 2 99 | assert f(5) == 0 100 | assert f(6) == 1 101 | assert f(7) == 0 102 | assert f(8) == 3 103 | assert f(9) == 0 104 | assert f((1 << 100) - 2) == 1 105 | assert f((1 << 100) - 1) == 0 106 | assert f((1 << 100)) == 100 107 | assert f((1 << 100) + 1) == 0 108 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cirq-unstable==0.8.0.dev20200313203209 2 | pytest 3 | --------------------------------------------------------------------------------