├── README.md ├── experiments ├── addition │ ├── adder.go │ ├── adder_test.go │ ├── main.go │ ├── py_experiment │ │ └── check_exists.py │ └── sliding.go └── brute_gate │ ├── add.go │ ├── main.go │ └── search.go └── quantum ├── arithmetic.go ├── arithmetic_test.go ├── computer.go ├── computer_test.go ├── cond.go ├── cond_test.go ├── gates.go ├── hash.go ├── hash_test.go ├── inversion.go ├── inversion_test.go ├── linalg.go ├── linalg_test.go ├── primitives.go ├── primitives_test.go ├── reg.go ├── render.go ├── search.go ├── toffoli.go └── toffoli_test.go /README.md: -------------------------------------------------------------------------------- 1 | # learn-quantum 2 | 3 | Learning a little bit about quantum computing. 4 | 5 | So far, I have made: 6 | 7 | * A simulator for universal quantum computers. 8 | * Some useful gates built on top of quantum primitives. 9 | * A bi-directional search program to derive gates like the Toffoli gate. 10 | 11 | # Learning Resources 12 | 13 | * [Quantum computing for the very curious](https://quantum.country/qcvc) 14 | * [An Efficient Methodology for Mapping Quantum Circuits to the IBM QX Architectures](https://arxiv.org/abs/1712.04722) - translating general quantum circuits to IBM QX. 15 | * [Elementary gates for quantum computation](https://arxiv.org/abs/quant-ph/9503016) - how to implement n-bit Toffoli gate. 16 | * [Improved Quantum Cost for n-bit Toffoli gates](https://arxiv.org/abs/quant-ph/0403053) 17 | * [Quantum Addition Circuits and Unbounded Fan-Out](https://arxiv.org/abs/0910.2530) 18 | * [Asymptotically Efficient Quantum Karatsuba Multiplication](https://arxiv.org/abs/1904.07356) 19 | -------------------------------------------------------------------------------- /experiments/addition/adder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/unixpickle/learn-quantum/quantum" 5 | ) 6 | 7 | type AddGate struct{} 8 | 9 | func (_ AddGate) String() string { 10 | return "Add" 11 | } 12 | 13 | func (_ AddGate) Apply(c quantum.Computer) { 14 | s := c.(*quantum.Simulation) 15 | s1 := s.Copy() 16 | for i, phase := range s1.Phases { 17 | a, b := adderPairs(s.NumBits(), i) 18 | dest := replaceAddResult(s.NumBits(), i, a+b) 19 | s.Phases[dest] = phase 20 | } 21 | } 22 | 23 | func (_ AddGate) Inverse() quantum.Gate { 24 | return SubGate{} 25 | } 26 | 27 | type SubGate struct{} 28 | 29 | func (_ SubGate) String() string { 30 | return "Sub" 31 | } 32 | 33 | func (_ SubGate) Apply(c quantum.Computer) { 34 | s := c.(*quantum.Simulation) 35 | s1 := s.Copy() 36 | for i, phase := range s1.Phases { 37 | a, b := adderPairs(s.NumBits(), i) 38 | dest := replaceAddResult(s.NumBits(), i, b-a) 39 | s.Phases[dest] = phase 40 | } 41 | } 42 | 43 | func (_ SubGate) Inverse() quantum.Gate { 44 | return AddGate{} 45 | } 46 | 47 | func adderPairs(numBits, state int) (uint32, uint32) { 48 | var state1 uint32 49 | var state2 uint32 50 | for i := 0; i < numBits; i++ { 51 | if i&1 == 0 { 52 | state1 |= uint32(state&(1<> uint(i/2) 53 | } else { 54 | state2 |= uint32(state&(1<> uint(i/2+1) 55 | } 56 | } 57 | return state1, state2 58 | } 59 | 60 | func replaceAddResult(numBits, state int, state2 uint32) int { 61 | for i := 1; i < numBits; i += 2 { 62 | mask := 1 << uint(i) 63 | if state2&(1< 1e-8 { 21 | t.Error("incorrect end state:", qc.Sample()) 22 | } 23 | } 24 | 25 | func TestSubtractor(t *testing.T) { 26 | for i := 0; i < 100; i++ { 27 | state := rand.Intn(1 << 10) 28 | qc := quantum.NewSimulationBits(10, uint(state)) 29 | AddGate{}.Apply(qc) 30 | SubGate{}.Apply(qc) 31 | if cmplx.Abs(qc.Phases[state]-1) > 1e-8 { 32 | t.Error("incorrect end state") 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /experiments/addition/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/unixpickle/learn-quantum/quantum" 7 | ) 8 | 9 | const ( 10 | BitSize = 5 11 | NumBits = BitSize * 2 12 | MaxCache = 5000000 13 | ) 14 | 15 | func main() { 16 | hasher := quantum.NewCircuitHasher(NumBits) 17 | gen := quantum.NewCircuitGen(4, generateBasis(), MaxCache) 18 | backward := quantum.NewBackwardsMap(hasher, AddGate{}) 19 | 20 | for i := 1; i < 5; i++ { 21 | ch, count := gen.Generate(i) 22 | fmt.Println("Doing backward search of depth", i, "with", count, "permutations...") 23 | for c := range ch { 24 | c1 := append(quantum.Circuit{}, c...) 25 | for j, g := range c { 26 | c1[j] = &EndGate{Gate: g} 27 | } 28 | backward.AddCircuit(c1) 29 | } 30 | } 31 | 32 | for i := 1; i < 100; i++ { 33 | ch, count := gen.Generate(i) 34 | fmt.Println("Doing forward search of depth", i, "with", count, "permutations...") 35 | for c := range ch { 36 | forward := &SlidingGate{Gate: c} 37 | if tail := backward.Lookup(forward); tail != nil { 38 | fmt.Println(append(quantum.Circuit{forward}, tail...)) 39 | return 40 | } 41 | } 42 | } 43 | } 44 | 45 | func generateBasis() []quantum.Gate { 46 | const numBits = 4 47 | var result []quantum.Gate 48 | for i := 0; i < numBits; i++ { 49 | result = append(result, &quantum.HGate{Bit: i}) 50 | result = append(result, &quantum.TGate{Bit: i}) 51 | result = append(result, &quantum.TGate{Bit: i, Conjugate: true}) 52 | result = append(result, &quantum.XGate{Bit: i}) 53 | result = append(result, &quantum.YGate{Bit: i}) 54 | result = append(result, &quantum.ZGate{Bit: i}) 55 | for j := 0; j < numBits; j++ { 56 | if j != i { 57 | result = append(result, &quantum.CNotGate{Control: i, Target: j}) 58 | result = append(result, &quantum.CSqrtNotGate{Control: i, Target: j}) 59 | result = append(result, &quantum.CSqrtNotGate{Control: i, Target: j, Invert: true}) 60 | result = append(result, &quantum.CHGate{Control: i, Target: j}) 61 | if i < j { 62 | for k := 0; k < numBits; k++ { 63 | if k != i && k != j { 64 | result = append(result, &quantum.CCNotGate{ 65 | Control1: i, 66 | Control2: j, 67 | Target: k, 68 | }) 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | return result 76 | } 77 | -------------------------------------------------------------------------------- /experiments/addition/py_experiment/check_exists.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use projected gradient descent to uncover factorized 3 | quantum circuits for addition. 4 | """ 5 | 6 | import numpy as np 7 | import torch 8 | import torch.nn as nn 9 | import torch.optim as optim 10 | 11 | SUM_BITS = 5 12 | NUM_BITS = SUM_BITS * 2 13 | 14 | 15 | def main(): 16 | target_matrix = compute_target_matrix() 17 | start = ComplexMatrix.random(16) 18 | forward = ComplexMatrix.random(16) 19 | middle = ComplexMatrix.random(16) 20 | backward = ComplexMatrix.random(16) 21 | forward2 = ComplexMatrix.random(16) 22 | middle2 = ComplexMatrix.random(16) 23 | backward2 = ComplexMatrix.random(16) 24 | end = ComplexMatrix.random(16) 25 | 26 | # Check that expanding still produces a unitary matrix. 27 | # x = forward.expander(5, [0, 1, 2, 3])() 28 | # print(x.mul(x.H()).real) 29 | 30 | matrices = [start, forward, middle, backward, forward2, middle2, backward2, end] 31 | expanders = [ 32 | start.expander(NUM_BITS, list(range(0, 4))), 33 | 34 | sliding_expander(forward), 35 | middle.expander(NUM_BITS, list(range(NUM_BITS - 4, NUM_BITS))), 36 | sliding_expander(backward, forward=False), 37 | 38 | sliding_expander(forward2), 39 | middle2.expander(NUM_BITS, list(range(NUM_BITS - 4, NUM_BITS))), 40 | sliding_expander(backward2, forward=False), 41 | 42 | end.expander(NUM_BITS, list(range(0, 4))), 43 | ] 44 | sgd = optim.SGD([(m.real, m.imag)[i] for m in matrices for i in [0, 1]], lr=200) 45 | 46 | while True: 47 | product = expanders[0]() 48 | for e in expanders[1:]: 49 | product = e().mul(product) 50 | all_diffs = torch.pow(target_matrix - product.real, 2) 51 | approx_loss = torch.mean(all_diffs * torch.rand_like(all_diffs)) 52 | exact_loss = torch.mean(all_diffs) 53 | sgd.zero_grad() 54 | approx_loss.backward() 55 | sgd.step() 56 | print('loss=%.8f' % exact_loss.item()) 57 | for m in matrices: 58 | m.orthogonalize() 59 | 60 | 61 | class ComplexMatrix: 62 | def __init__(self, real, imag): 63 | self.real = real 64 | self.imag = imag 65 | 66 | @classmethod 67 | def random(cls, size): 68 | res = cls(nn.Parameter(torch.randn(size, size)), 69 | nn.Parameter(torch.randn(size, size))) 70 | res.orthogonalize() 71 | return res 72 | 73 | @classmethod 74 | def eye(cls, size): 75 | eye = torch.eye(size) 76 | return cls(eye, torch.zeros_like(eye)) 77 | 78 | def H(self): 79 | return ComplexMatrix(self.real.transpose(1, 0), -self.imag.transpose(1, 0)) 80 | 81 | def expander(self, num_bits, bit_indices): 82 | """ 83 | Generate a function that expands this matrix. 84 | """ 85 | exp = expand_operator(num_bits, bit_indices) 86 | 87 | def expand(): 88 | return ComplexMatrix(exp(self.real), exp(self.imag)) 89 | 90 | return expand 91 | 92 | def mul(self, other): 93 | """ 94 | Generate (self @ other). 95 | """ 96 | return ComplexMatrix(torch.matmul(self.real, other.real) - 97 | torch.matmul(self.imag, other.imag), 98 | torch.matmul(self.real, other.imag) + 99 | torch.matmul(self.imag, other.real)) 100 | 101 | def orthogonalize(self): 102 | """ 103 | Project this matrix onto the space of unitary 104 | matrices. 105 | """ 106 | real = self.real.detach().cpu().numpy() 107 | imag = self.imag.detach().cpu().numpy() 108 | full = real.astype(np.complex) + 1j * imag.astype(np.complex) 109 | u, _, vh = np.linalg.svd(full) 110 | orthog = np.dot(u, vh) 111 | real = np.real(orthog).astype(np.float32) 112 | imag = np.imag(orthog).astype(np.float32) 113 | self.real.data = torch.from_numpy(real) 114 | self.imag.data = torch.from_numpy(imag) 115 | 116 | 117 | def sliding_expander(matrix, forward=True): 118 | expanders = [] 119 | for i in (range(0, NUM_BITS - 3, 2) if forward else range(NUM_BITS - 4, -1, -2)): 120 | expanders.append(matrix.expander(NUM_BITS, [i, i+1, i+2, i+3])) 121 | 122 | def fn(): 123 | res = ComplexMatrix.eye(1 << NUM_BITS) 124 | for x in expanders: 125 | res = x().mul(res) 126 | return res 127 | 128 | return fn 129 | 130 | 131 | def compute_target_matrix(): 132 | """ 133 | Compute the ground-truth unitary matrix for addition. 134 | """ 135 | target_np = np.zeros([1 << NUM_BITS, 1 << NUM_BITS], dtype=np.float32) 136 | for i in range(1 << NUM_BITS): 137 | n1 = 0 138 | n2 = 0 139 | for j in range(SUM_BITS): 140 | n1 |= (0 if i & (1 << (2 * j)) == 0 else 1) << j 141 | n2 |= (0 if i & (1 << (2 * j + 1)) == 0 else 1) << j 142 | result = (n1 + n2) & ((1 << SUM_BITS) - 1) 143 | target_idx = 0 144 | for j in range(SUM_BITS): 145 | if n1 & (1 << j) != 0: 146 | target_idx |= (1 << (2 * j)) 147 | if result & (1 << j) != 0: 148 | target_idx |= (1 << (2 * j + 1)) 149 | target_np[target_idx, i] = 1 150 | return torch.from_numpy(target_np) 151 | 152 | 153 | def expand_operator(num_bits, bit_indices): 154 | """ 155 | Generate a function that takes small unitary matrices 156 | to larger unitary matrices that act on the bits in the 157 | bit_indices list. 158 | 159 | Args: 160 | num_bits: the number of bits in the bigger matrix. 161 | bit_indices: indices mapping bits from the smaller 162 | matrix to those of the bigger one. 163 | 164 | Returns: 165 | A function that takes a small matrix and produces 166 | a larger one. 167 | """ 168 | bit_mask = 0 169 | for idx in bit_indices: 170 | bit_mask |= 1 << idx 171 | inv_mask = ((1 << num_bits) - 1) ^ bit_mask 172 | 173 | def small_idx(large_idx): 174 | num = 0 175 | for i, idx in enumerate(bit_indices): 176 | if large_idx & (1 << idx) != 0: 177 | num |= 1 << i 178 | return num 179 | 180 | source_inds = [] 181 | zero_index = 1 << (2 * len(bit_indices)) 182 | for row in range(1 << num_bits): 183 | for col in range(1 << num_bits): 184 | if row & inv_mask != col & inv_mask: 185 | source_inds.append(zero_index) 186 | else: 187 | source_idx = small_idx(col) 188 | dest_idx = small_idx(row) 189 | source_inds.append(source_idx + dest_idx * (1 << len(bit_indices))) 190 | 191 | def fn(small_matrix): 192 | flat = torch.cat([small_matrix.view(-1), torch.zeros_like(small_matrix[0, 0:1])]) 193 | flat_big = flat[source_inds] 194 | return flat_big.view(1 << num_bits, 1 << num_bits) 195 | 196 | return fn 197 | 198 | 199 | if __name__ == '__main__': 200 | main() 201 | -------------------------------------------------------------------------------- /experiments/addition/sliding.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/unixpickle/learn-quantum/quantum" 4 | 5 | // A SlidingGate is a 4-qubit gate that is run 6 | // sequentially with stride 2. 7 | type SlidingGate struct { 8 | Gate quantum.Gate 9 | Invert bool 10 | } 11 | 12 | func (s *SlidingGate) String() string { 13 | return "SlidingGate(" + s.Gate.String() + ")" 14 | } 15 | 16 | func (s *SlidingGate) Apply(c quantum.Computer) { 17 | numPairs := c.NumBits() / 2 18 | if s.Invert { 19 | inv := s.Gate.Inverse() 20 | for i := numPairs - 2; i >= 0; i-- { 21 | start := i * 2 22 | mapped := &quantum.MappedComputer{ 23 | C: c, 24 | Mapping: []int{start, start + 1, start + 2, start + 3}, 25 | } 26 | inv.Apply(mapped) 27 | } 28 | } else { 29 | for i := 0; i < numPairs-1; i++ { 30 | start := i * 2 31 | mapped := &quantum.MappedComputer{ 32 | C: c, 33 | Mapping: []int{start, start + 1, start + 2, start + 3}, 34 | } 35 | s.Gate.Apply(mapped) 36 | } 37 | } 38 | } 39 | 40 | func (s *SlidingGate) Inverse() quantum.Gate { 41 | return &SlidingGate{Gate: s.Gate, Invert: !s.Invert} 42 | } 43 | 44 | // An EndGate is a 4-qubit gate that is run on the final 45 | // qubits in a circuit. 46 | type EndGate struct { 47 | Gate quantum.Gate 48 | } 49 | 50 | func (e *EndGate) String() string { 51 | return "EndGate(" + e.Gate.String() + ")" 52 | } 53 | 54 | func (e *EndGate) Apply(c quantum.Computer) { 55 | i := c.NumBits() - 4 56 | mapped := &quantum.MappedComputer{ 57 | C: c, 58 | Mapping: []int{i, i + 1, i + 2, i + 3}, 59 | } 60 | e.Gate.Apply(mapped) 61 | } 62 | 63 | func (e *EndGate) Inverse() quantum.Gate { 64 | return &EndGate{Gate: e.Gate.Inverse()} 65 | } 66 | -------------------------------------------------------------------------------- /experiments/brute_gate/add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/unixpickle/learn-quantum/quantum" 4 | 5 | type constAdder struct { 6 | Value uint 7 | Target quantum.Reg 8 | Carry *int 9 | Invert bool 10 | } 11 | 12 | func (c *constAdder) String() string { 13 | return "ConstAdder" 14 | } 15 | 16 | func (c *constAdder) Apply(comp quantum.Computer) { 17 | sim := comp.(*quantum.Simulation) 18 | newPhases := make([]complex128, len(sim.Phases)) 19 | for i, ph := range sim.Phases { 20 | input := c.Target.Extract(uint(i)) 21 | var sum uint 22 | if c.Invert { 23 | sum = input - c.Value 24 | } else { 25 | sum = input + c.Value 26 | } 27 | carryBit := sum & (1 << uint(len(c.Target))) 28 | sum &= (1 << uint(len(c.Target))) - 1 29 | newState := uint(i) 30 | newState = c.Target.Inject(newState, sum) 31 | if c.Carry != nil && carryBit != 0 { 32 | newState ^= 1 << uint(*c.Carry) 33 | } 34 | newPhases[newState] = ph 35 | } 36 | sim.Phases = newPhases 37 | } 38 | 39 | func (c *constAdder) Inverse() quantum.Gate { 40 | c1 := *c 41 | c1.Invert = !c1.Invert 42 | return &c1 43 | } 44 | -------------------------------------------------------------------------------- /experiments/brute_gate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/unixpickle/learn-quantum/quantum" 7 | ) 8 | 9 | func main() { 10 | // fmt.Println(Search(3, AllGates(3, false), quantum.NewClassicalGate(Toffoli, ""))) 11 | // fmt.Println(SearchSqrt(1, AllGates(1, false), quantum.NewClassicalGate(Not, ""))) 12 | // fmt.Println(SearchCtrl(2, AllGates(2, false), &quantum.SqrtNotGate{Bit: 1})) 13 | // fmt.Println(SearchCtrl(2, AllGates(2, false), &quantum.HGate{Bit: 1})) 14 | idx := 3 15 | fmt.Println(Search(4, AllGates(4, true), &constAdder{ 16 | Value: 5, 17 | Target: quantum.Reg{0, 1, 2}, 18 | Carry: &idx, 19 | })) 20 | } 21 | 22 | func Not(b []bool) []bool { 23 | res := make([]bool, 1) 24 | copy(res, b) 25 | res[0] = !res[0] 26 | return res 27 | } 28 | 29 | func Toffoli(b []bool) []bool { 30 | res := make([]bool, 3) 31 | copy(res, b) 32 | if res[0] && res[1] { 33 | res[2] = !res[2] 34 | } 35 | return res 36 | } 37 | 38 | func Or(b []bool) []bool { 39 | res := make([]bool, 3) 40 | copy(res, b) 41 | if res[0] || res[1] { 42 | res[2] = !res[2] 43 | } 44 | return res 45 | } 46 | 47 | func CNot(b []bool) []bool { 48 | res := make([]bool, 3) 49 | copy(res, b) 50 | if res[0] { 51 | res[1] = !res[1] 52 | } 53 | return res 54 | } 55 | 56 | func Swap(b []bool) []bool { 57 | res := make([]bool, 2) 58 | res[0] = b[1] 59 | res[1] = b[0] 60 | return res 61 | } 62 | 63 | func CSwap(b []bool) []bool { 64 | res := make([]bool, 3) 65 | copy(res, b) 66 | if b[0] { 67 | res[1] = b[2] 68 | res[2] = b[1] 69 | } 70 | return res 71 | } 72 | -------------------------------------------------------------------------------- /experiments/brute_gate/search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/unixpickle/learn-quantum/quantum" 7 | ) 8 | 9 | const maxCircuitCache = 5000000 10 | 11 | func AllGates(numBits int, includeCCNot bool) []quantum.Gate { 12 | var result []quantum.Gate 13 | for i := 0; i < numBits; i++ { 14 | result = append(result, &quantum.HGate{Bit: i}) 15 | result = append(result, &quantum.TGate{Bit: i}) 16 | result = append(result, &quantum.TGate{Bit: i, Conjugate: true}) 17 | result = append(result, &quantum.XGate{Bit: i}) 18 | result = append(result, &quantum.YGate{Bit: i}) 19 | result = append(result, &quantum.ZGate{Bit: i}) 20 | for j := 0; j < numBits; j++ { 21 | if j != i { 22 | result = append(result, &quantum.CNotGate{Control: i, Target: j}) 23 | if includeCCNot && i < j { 24 | for k := 0; k < numBits; k++ { 25 | if k != i && k != j { 26 | result = append(result, &quantum.CCNotGate{ 27 | Control1: i, 28 | Control2: j, 29 | Target: k, 30 | }) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | return result 38 | } 39 | 40 | func Search(numBits int, gates []quantum.Gate, target quantum.Gate) quantum.Circuit { 41 | gen := quantum.NewCircuitGen(numBits, gates, maxCircuitCache) 42 | hasher := quantum.NewCircuitHasher(numBits) 43 | goal := hasher.Hash(target) 44 | backwards := quantum.NewBackwardsMap(hasher, target) 45 | 46 | for i := 1; true; i++ { 47 | circuits := gen.GenerateSlice(i) 48 | if circuits == nil { 49 | break 50 | } 51 | fmt.Println("Doing backward search of depth", i, "with", len(circuits), "permutations...") 52 | for _, c := range circuits { 53 | if hasher.Hash(c) == goal { 54 | return c 55 | } 56 | backwards.AddCircuit(c) 57 | } 58 | } 59 | 60 | for i := 1; i < 100; i++ { 61 | ch, count := gen.Generate(i) 62 | fmt.Println("Doing forward search of depth", i, "with", count, "permutations...") 63 | for c := range ch { 64 | if c1 := backwards.Lookup(c); c1 != nil { 65 | return append(c, c1...) 66 | } 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func SearchSqrt(numBits int, gates []quantum.Gate, target quantum.Gate) quantum.Circuit { 74 | gen := quantum.NewCircuitGen(numBits, gates, maxCircuitCache) 75 | hasher := quantum.NewCircuitHasher(numBits) 76 | goal := hasher.Hash(target) 77 | 78 | for i := 1; i < 100; i++ { 79 | ch, count := gen.Generate(i) 80 | fmt.Println("Doing forward search of depth", i, "with", count, "permutations...") 81 | for c := range ch { 82 | c1 := append(append(quantum.Circuit{}, c...), c...) 83 | if hasher.Hash(c1) == goal { 84 | return c 85 | } 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func SearchCtrl(numBits int, gates []quantum.Gate, gate quantum.Gate) quantum.Circuit { 93 | return Search(numBits, gates, &ctrlGate{gate}) 94 | } 95 | 96 | type ctrlGate struct { 97 | G quantum.Gate 98 | } 99 | 100 | func (c *ctrlGate) Apply(qc quantum.Computer) { 101 | qc.(*quantum.Simulation).ControlGate(0, c.G) 102 | } 103 | 104 | func (c *ctrlGate) Inverse() quantum.Gate { 105 | return &ctrlGate{G: c.G.Inverse()} 106 | } 107 | 108 | func (c *ctrlGate) String() string { 109 | return "Ctrl(" + c.G.String() + ")" 110 | } 111 | -------------------------------------------------------------------------------- /quantum/arithmetic.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | // Add performs basic addition in base 2. 4 | // The value stored in source is added to the value stored 5 | // in target. If the carry argument is non-nil, it is used 6 | // as the qubit to be flipped if the addition wraps. 7 | // The source and target must be the same number of bits. 8 | // The source and target registers are stored lowest-bit 9 | // first. 10 | func Add(c Computer, source, target Reg, carry *int) { 11 | var carryReg Reg 12 | if carry != nil { 13 | carryReg = Reg{*carry} 14 | } 15 | if source.Overlaps(target) || source.Overlaps(carryReg) || target.Overlaps(carryReg) || 16 | len(source) != len(target) || !source.Valid() || !target.Valid() { 17 | panic("invalid arguments") 18 | } 19 | 20 | if len(source) == 1 { 21 | if carry != nil { 22 | CCNot(c, source[0], target[0], *carry) 23 | } 24 | c.CNot(source[0], target[0]) 25 | return 26 | } 27 | 28 | // Implementation of https://arxiv.org/abs/0910.2530 29 | 30 | // Step 1 31 | for i := 1; i < len(source); i++ { 32 | c.CNot(source[i], target[i]) 33 | } 34 | 35 | // Step 2 36 | if carry != nil { 37 | c.CNot(source[len(source)-1], *carry) 38 | } 39 | for i := len(source) - 2; i > 0; i-- { 40 | c.CNot(source[i], source[i+1]) 41 | } 42 | 43 | // Step 3 44 | for i := 0; i < len(source)-1; i++ { 45 | CCNot(c, source[i], target[i], source[i+1]) 46 | } 47 | if carry != nil { 48 | CCNot(c, source[len(source)-1], target[len(target)-1], *carry) 49 | } 50 | 51 | // Step 4 52 | for i := len(source) - 1; i > 0; i-- { 53 | c.CNot(source[i], target[i]) 54 | CCNot(c, source[i-1], target[i-1], source[i]) 55 | } 56 | 57 | // Step 5 58 | for i := 1; i < len(source)-1; i++ { 59 | c.CNot(source[i], source[i+1]) 60 | } 61 | 62 | // Step 6 63 | for i := 0; i < len(source); i++ { 64 | c.CNot(source[i], target[i]) 65 | } 66 | } 67 | 68 | // Sub performs the inverse of Add. 69 | func Sub(c Computer, source, target Reg, carry *int) { 70 | var carryReg Reg 71 | if carry != nil { 72 | carryReg = Reg{*carry} 73 | } 74 | if source.Overlaps(target) || source.Overlaps(carryReg) || target.Overlaps(carryReg) || 75 | len(source) != len(target) || !source.Valid() || !target.Valid() { 76 | panic("invalid arguments") 77 | } 78 | 79 | if len(source) == 1 { 80 | c.CNot(source[0], target[0]) 81 | if carry != nil { 82 | CCNot(c, source[0], target[0], *carry) 83 | } 84 | return 85 | } 86 | 87 | // Step 6 88 | for i := len(source) - 1; i >= 0; i-- { 89 | c.CNot(source[i], target[i]) 90 | } 91 | 92 | // Step 5 93 | for i := len(source) - 2; i > 0; i-- { 94 | c.CNot(source[i], source[i+1]) 95 | } 96 | 97 | // Step 4 98 | for i := 1; i < len(source); i++ { 99 | CCNot(c, source[i-1], target[i-1], source[i]) 100 | c.CNot(source[i], target[i]) 101 | } 102 | 103 | // Step 3 104 | if carry != nil { 105 | CCNot(c, source[len(source)-1], target[len(target)-1], *carry) 106 | } 107 | for i := len(source) - 2; i >= 0; i-- { 108 | CCNot(c, source[i], target[i], source[i+1]) 109 | } 110 | 111 | // Step 2 112 | for i := 1; i < len(source)-1; i++ { 113 | c.CNot(source[i], source[i+1]) 114 | } 115 | if carry != nil { 116 | c.CNot(source[len(source)-1], *carry) 117 | } 118 | 119 | // Step 1 120 | for i := len(source) - 1; i > 0; i-- { 121 | c.CNot(source[i], target[i]) 122 | } 123 | } 124 | 125 | // Lt flips a bit if a < b in unsigned arithmetic. 126 | func Lt(c Computer, a, b Reg, target int) { 127 | Sub(c, b, a, &target) 128 | Add(c, b, a, nil) 129 | } 130 | 131 | // ModAdd performs modular addition. 132 | // 133 | // Behavior is undefined if the inputs are greater than or 134 | // equal to the modulus. 135 | // 136 | // The working qubits must start as zeros and will end as 137 | // zeros. 138 | func ModAdd(c Computer, source, target, modulus Reg, working int) { 139 | workingReg := Reg{working} 140 | if len(source) != len(target) || len(source) != len(modulus) || !source.Valid() || 141 | !target.Valid() || !modulus.Valid() || source.Overlaps(target) || 142 | target.Overlaps(modulus) || source.Overlaps(workingReg) || target.Overlaps(workingReg) || 143 | modulus.Overlaps(workingReg) { 144 | panic("invalid inputs") 145 | } 146 | 147 | // Extend the target with one working bit, then add 148 | // the source to it. 149 | Add(c, source, target, &working) 150 | Sub(c, modulus, target, &working) 151 | 152 | // If the carry bit is set, it means we wrapped around 153 | // when subtracting the modulus. 154 | Cond(c, working, func(c Computer) { 155 | Add(c, modulus, target, nil) 156 | }) 157 | 158 | // Reverse working1 if it was set, using the 159 | // assumption that source and target started out both 160 | // less than the modulus. 161 | X(c, working) 162 | Lt(c, target, source, working) 163 | } 164 | 165 | // ModSub is the inverse of ModAdd. 166 | func ModSub(c Computer, source, target, modulus Reg, working int) { 167 | workingReg := Reg{working} 168 | if len(source) != len(target) || len(source) != len(modulus) || !source.Valid() || 169 | !target.Valid() || !modulus.Valid() || source.Overlaps(target) || 170 | target.Overlaps(modulus) || source.Overlaps(workingReg) || target.Overlaps(workingReg) || 171 | modulus.Overlaps(workingReg) { 172 | panic("invalid inputs") 173 | } 174 | 175 | Lt(c, target, source, working) 176 | X(c, working) 177 | Cond(c, working, func(c Computer) { 178 | Sub(c, modulus, target, nil) 179 | }) 180 | Add(c, modulus, target, &working) 181 | Sub(c, source, target, &working) 182 | } 183 | -------------------------------------------------------------------------------- /quantum/arithmetic_test.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestAdd(t *testing.T) { 9 | for _, carry := range []bool{false, true} { 10 | name := "NoCarry" 11 | if carry { 12 | name = "Carry" 13 | } 14 | t.Run(name, func(t *testing.T) { 15 | for numBits := 1; numBits < 7; numBits++ { 16 | for i := 0; i < 10; i++ { 17 | s1 := RandomSimulation(numBits*2 + 1) 18 | s2 := s1.Copy() 19 | bits := rand.Perm(s1.NumBits()) 20 | source := Reg(bits[:numBits]) 21 | target := Reg(bits[numBits : numBits*2]) 22 | var carryField *int 23 | if carry { 24 | carryField = &bits[numBits*2] 25 | } 26 | Add(s1, source, target, carryField) 27 | simulatedAdd(s2, source, target, carryField) 28 | if !s1.ApproxEqual(s2, 1e-8) { 29 | t.Error("bad results", numBits) 30 | } 31 | } 32 | } 33 | }) 34 | } 35 | } 36 | 37 | func TestSub(t *testing.T) { 38 | for _, carry := range []bool{false, true} { 39 | name := "NoCarry" 40 | if carry { 41 | name = "Carry" 42 | } 43 | t.Run(name, func(t *testing.T) { 44 | for numBits := 1; numBits < 7; numBits++ { 45 | for i := 0; i < 10; i++ { 46 | s1 := RandomSimulation(numBits*2 + 1) 47 | s2 := s1.Copy() 48 | bits := rand.Perm(s1.NumBits()) 49 | source := Reg(bits[:numBits]) 50 | target := Reg(bits[numBits : numBits*2]) 51 | var carryField *int 52 | if carry { 53 | carryField = &bits[numBits*2] 54 | } 55 | Add(s1, source, target, carryField) 56 | Sub(s1, source, target, carryField) 57 | if !s1.ApproxEqual(s2, 1e-8) { 58 | t.Error("bad results", numBits) 59 | } 60 | } 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestLt(t *testing.T) { 67 | for numBits := 1; numBits < 7; numBits++ { 68 | for i := 0; i < 10; i++ { 69 | s1 := RandomSimulation(numBits*2 + 1) 70 | s2 := s1.Copy() 71 | bits := rand.Perm(s1.NumBits()) 72 | a := Reg(bits[:numBits]) 73 | b := Reg(bits[numBits : numBits*2]) 74 | target := bits[numBits*2] 75 | Lt(s1, a, b, target) 76 | simulatedLt(s2, a, b, target) 77 | if !s1.ApproxEqual(s2, 1e-8) { 78 | t.Error("bad results", numBits) 79 | } 80 | } 81 | } 82 | } 83 | 84 | func TestModAdd(t *testing.T) { 85 | for numBits := 1; numBits < 5; numBits++ { 86 | for i := 0; i < 10; i++ { 87 | s1 := RandomSimulation(numBits*3 + 1) 88 | bits := rand.Perm(s1.NumBits()) 89 | source := Reg(bits[:numBits]) 90 | target := Reg(bits[numBits : numBits*2]) 91 | modulus := Reg(bits[numBits*2 : numBits*3]) 92 | working := bits[numBits*3] 93 | workingReg := Reg{working} 94 | for i := range s1.Phases { 95 | if source.Extract(uint(i)) >= modulus.Extract(uint(i)) || 96 | target.Extract(uint(i)) >= modulus.Extract(uint(i)) || 97 | modulus.Extract(uint(i)) == 0 || workingReg.Extract(uint(i)) != 0 { 98 | s1.Phases[i] = 0 99 | } 100 | } 101 | s2 := s1.Copy() 102 | ModAdd(s1, source, target, modulus, working) 103 | simulatedModAdd(s2, source, target, modulus) 104 | if !s1.ApproxEqual(s2, 1e-8) { 105 | t.Error("bad results", numBits) 106 | } 107 | } 108 | } 109 | } 110 | 111 | func TestModSub(t *testing.T) { 112 | for numBits := 1; numBits < 5; numBits++ { 113 | for i := 0; i < 10; i++ { 114 | s1 := RandomSimulation(numBits*3 + 1) 115 | s2 := s1.Copy() 116 | bits := rand.Perm(s1.NumBits()) 117 | source := Reg(bits[:numBits]) 118 | target := Reg(bits[numBits : numBits*2]) 119 | modulus := Reg(bits[numBits*2 : numBits*3]) 120 | working := bits[numBits*3] 121 | ModAdd(s1, source, target, modulus, working) 122 | ModSub(s1, source, target, modulus, working) 123 | if !s1.ApproxEqual(s2, 1e-8) { 124 | t.Error("bad results", numBits) 125 | } 126 | } 127 | } 128 | } 129 | 130 | func simulatedAdd(sim *Simulation, source, target Reg, carry *int) { 131 | newPhases := make([]complex128, len(sim.Phases)) 132 | for i, ph := range sim.Phases { 133 | n1 := source.Extract(uint(i)) 134 | n2 := target.Extract(uint(i)) 135 | sum := n1 + n2 136 | carryBit := sum & (1 << uint(len(source))) 137 | sum &= (1 << uint(len(source))) - 1 138 | newState := uint(i) 139 | newState = target.Inject(newState, sum) 140 | if carry != nil && carryBit != 0 { 141 | newState ^= 1 << uint(*carry) 142 | } 143 | newPhases[newState] = ph 144 | } 145 | sim.Phases = newPhases 146 | } 147 | 148 | func simulatedLt(sim *Simulation, a, b Reg, target int) { 149 | newPhases := make([]complex128, len(sim.Phases)) 150 | for i, ph := range sim.Phases { 151 | n1 := a.Extract(uint(i)) 152 | n2 := b.Extract(uint(i)) 153 | newState := uint(i) 154 | if n1 < n2 { 155 | newState ^= 1 << uint(target) 156 | } 157 | newPhases[newState] = ph 158 | } 159 | sim.Phases = newPhases 160 | } 161 | 162 | func simulatedModAdd(sim *Simulation, source, target, modulus Reg) { 163 | newPhases := make([]complex128, len(sim.Phases)) 164 | for i, ph := range sim.Phases { 165 | if ph == 0 { 166 | continue 167 | } 168 | n1 := source.Extract(uint(i)) 169 | n2 := target.Extract(uint(i)) 170 | m := modulus.Extract(uint(i)) 171 | sum := (n1 + n2) % m 172 | newState := uint(i) 173 | newState = target.Inject(newState, sum) 174 | newPhases[newState] = ph 175 | } 176 | sim.Phases = newPhases 177 | } 178 | -------------------------------------------------------------------------------- /quantum/computer.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/cmplx" 7 | "math/rand" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | const epsilon = 1e-8 13 | 14 | // A Computer is a generic quantum computer. 15 | type Computer interface { 16 | NumBits() int 17 | InUse(bit int) bool 18 | Measure(bitIdx int) bool 19 | Unitary(target int, m *Matrix2) 20 | CNot(control, target int) 21 | } 22 | 23 | // A Simulation is a classical simulation of a quantum 24 | // computer. 25 | type Simulation struct { 26 | numBits int 27 | Phases []complex128 28 | } 29 | 30 | // Create a new Simulation with all qubits set to 0. 31 | func NewSimulation(numBits int) *Simulation { 32 | return NewSimulationBits(numBits, 0) 33 | } 34 | 35 | // Create a new Simulation with a given bit-string. 36 | func NewSimulationBits(numBits int, value uint) *Simulation { 37 | s := &Simulation{ 38 | numBits: numBits, 39 | Phases: make([]complex128, 1<= s.numBits { 66 | panic("bit index out of range") 67 | } 68 | return false 69 | } 70 | 71 | func (s *Simulation) Measure(bitIdx int) bool { 72 | var zeroProb float64 73 | var oneProb float64 74 | for i, ph := range s.Phases { 75 | prob := math.Pow(cmplx.Abs(ph), 2) 76 | if i&(1< zeroProb 83 | var scale float64 84 | if isOne { 85 | scale = 1 / math.Sqrt(oneProb) 86 | } else { 87 | scale = 1 / math.Sqrt(zeroProb) 88 | } 89 | for i := range s.Phases { 90 | if (i&(1<= s.numBits { 101 | panic("bit index out of range") 102 | } 103 | 104 | // Optimization for T-like gates. 105 | if m.M11 == 1 && m.M12 == 0 && m.M21 == 0 { 106 | for i := range s.Phases { 107 | if i&(1<= s.numBits || target < 0 || target >= s.numBits { 129 | panic("bit index out of range") 130 | } 131 | if control == target { 132 | panic("overlapping control and target is not invertible") 133 | } 134 | controlMask := 1 << uint(control) 135 | targetMask := 1 << uint(target) 136 | for i := range s.Phases { 137 | if i&controlMask != 0 && i&targetMask == 0 { 138 | other := i | targetMask 139 | s.Phases[i], s.Phases[other] = s.Phases[other], s.Phases[i] 140 | } 141 | } 142 | } 143 | 144 | func (s *Simulation) Phase(value []bool) complex128 { 145 | var idx int 146 | for i, x := range value { 147 | if x { 148 | idx |= 1 << uint(i) 149 | } 150 | } 151 | return s.Phases[idx] 152 | } 153 | 154 | func (s *Simulation) Copy() *Simulation { 155 | res := &Simulation{ 156 | numBits: s.numBits, 157 | Phases: make([]complex128, len(s.Phases)), 158 | } 159 | for i, phase := range s.Phases { 160 | res.Phases[i] = phase 161 | } 162 | return res 163 | } 164 | 165 | func (s *Simulation) ApproxEqual(s1 *Simulation, tol float64) bool { 166 | for i, phase := range s.Phases { 167 | if cmplx.Abs(phase-s1.Phases[i]) > tol { 168 | return false 169 | } 170 | } 171 | return true 172 | } 173 | 174 | func (s *Simulation) String() string { 175 | pieces := []string{} 176 | for i, phase := range s.Phases { 177 | if cmplx.Abs(phase) < epsilon { 178 | continue 179 | } 180 | var coeff string 181 | if math.Abs(imag(phase)) < epsilon { 182 | coeff = formatFloat(real(phase)) 183 | } else if math.Abs(real(phase)) < epsilon { 184 | coeff = formatFloat(imag(phase)) + "i" 185 | } else { 186 | if imag(phase) > 0 { 187 | coeff = fmt.Sprintf("(%s+%si)", formatFloat(real(phase)), formatFloat(imag(phase))) 188 | } else { 189 | coeff = fmt.Sprintf("(%s-%si)", formatFloat(real(phase)), formatFloat(-imag(phase))) 190 | } 191 | } 192 | pieces = append(pieces, coeff+s.classicalString(i)) 193 | } 194 | return strings.Join(pieces, " + ") 195 | } 196 | 197 | func (s *Simulation) Sample() []bool { 198 | var res []bool 199 | for i := 0; i < s.numBits; i++ { 200 | res = append(res, s.Measure(i)) 201 | } 202 | return res 203 | } 204 | 205 | // ControlGate runs the gate g on states where control is 206 | // not set. 207 | // The gate g must not modify the control bit. 208 | func (s *Simulation) ControlGate(control int, g Gate) { 209 | s1 := s.Copy() 210 | for i := range s.Phases { 211 | if i&(1<> uint(j)) 230 | } 231 | return "|" + res + ">" 232 | } 233 | 234 | func formatFloat(f float64) string { 235 | res := fmt.Sprintf("%f", f) 236 | for strings.Contains(res, ".") && res[len(res)-1] == '0' { 237 | res = res[:len(res)-1] 238 | } 239 | if res[len(res)-1] == '.' { 240 | return res[:len(res)-1] 241 | } 242 | return res 243 | } 244 | 245 | // A MappedComputer is a Computer that contains a subset 246 | // of the qubits on some other computer. 247 | // It can be used to permute or reduce the number of 248 | // qubits that gates act on. 249 | type MappedComputer struct { 250 | C Computer 251 | 252 | // Mapping maps each qubit on this computer to qubits 253 | // in C. 254 | Mapping []int 255 | } 256 | 257 | func (m *MappedComputer) NumBits() int { 258 | return len(m.Mapping) 259 | } 260 | 261 | func (m *MappedComputer) InUse(bitIdx int) bool { 262 | return m.C.InUse(m.Mapping[bitIdx]) 263 | } 264 | 265 | func (m *MappedComputer) Measure(bitIdx int) bool { 266 | return m.C.Measure(m.Mapping[bitIdx]) 267 | } 268 | 269 | func (m *MappedComputer) Unitary(target int, mat *Matrix2) { 270 | m.C.Unitary(m.Mapping[target], mat) 271 | } 272 | 273 | func (m *MappedComputer) CNot(control, target int) { 274 | m.C.CNot(m.Mapping[control], m.Mapping[target]) 275 | } 276 | -------------------------------------------------------------------------------- /quantum/computer_test.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/cmplx" 7 | "math/rand" 8 | "testing" 9 | ) 10 | 11 | func ExampleSimulation() { 12 | // Create s1 = |+> |-> 13 | s1 := NewSimulation(2) 14 | X(s1, 1) 15 | H(s1, 0) 16 | H(s1, 1) 17 | 18 | // Create s2 = |-> |-> 19 | s2 := NewSimulation(2) 20 | X(s2, 0) 21 | X(s2, 1) 22 | H(s2, 0) 23 | H(s2, 1) 24 | 25 | // Apply CNot on s1. 26 | s1.CNot(0, 1) 27 | 28 | fmt.Println(s1) 29 | fmt.Println(s2) 30 | 31 | // Output: 32 | // 0.5|00> + -0.5|10> + -0.5|01> + 0.5|11> 33 | // 0.5|00> + -0.5|10> + -0.5|01> + 0.5|11> 34 | } 35 | 36 | func TestSimulationSample(t *testing.T) { 37 | s := NewSimulation(2) 38 | H(s, 0) 39 | s.CNot(0, 1) 40 | counts := map[int]int{} 41 | for i := 0; i < 100000; i++ { 42 | b := s.Copy().Sample() 43 | n := 0 44 | if b[0] { 45 | n |= 1 46 | } 47 | if b[1] { 48 | n |= 2 49 | } 50 | counts[n]++ 51 | } 52 | if counts[1] != 0 || counts[2] != 0 { 53 | t.Errorf("unexpected result") 54 | } 55 | // Stddev should be on the order of ~100. 56 | if math.Abs(float64(counts[0]-counts[3])) > 1000 { 57 | t.Errorf("incorrect sample counts, delta is %d", counts[0]-counts[3]) 58 | } 59 | } 60 | 61 | func TestInverses(t *testing.T) { 62 | s := RandomSimulation(8) 63 | original := s.Copy() 64 | makeX := func(idx int) func() { 65 | return func() { 66 | X(s, idx) 67 | } 68 | } 69 | makeY := func(idx int) func() { 70 | return func() { 71 | Y(s, idx) 72 | } 73 | } 74 | makeZ := func(idx int) func() { 75 | return func() { 76 | Z(s, idx) 77 | } 78 | } 79 | makeH := func(idx int) func() { 80 | return func() { 81 | H(s, idx) 82 | } 83 | } 84 | makeCNot := func(control, target int) func() { 85 | return func() { 86 | s.CNot(control, target) 87 | } 88 | } 89 | ops := []func(){} 90 | for i := 0; i < 1000; i++ { 91 | x := rand.Intn(5) 92 | if x == 0 { 93 | ops = append(ops, makeX(rand.Intn(8))) 94 | } else if x == 1 { 95 | ops = append(ops, makeH(rand.Intn(8))) 96 | } else if x == 2 { 97 | ops = append(ops, makeY(rand.Intn(8))) 98 | } else if x == 3 { 99 | ops = append(ops, makeZ(rand.Intn(8))) 100 | } else { 101 | n1 := rand.Intn(8) 102 | n2 := n1 103 | for n2 == n1 { 104 | n2 = rand.Intn(8) 105 | } 106 | ops = append(ops, makeCNot(n1, n2)) 107 | } 108 | } 109 | for _, op := range ops { 110 | op() 111 | } 112 | if s.ApproxEqual(original, epsilon) { 113 | t.Error("states should be nowhere close to equal") 114 | } 115 | for i := len(ops) - 1; i >= 0; i-- { 116 | ops[i]() 117 | } 118 | if !s.ApproxEqual(original, epsilon) { 119 | t.Error("states not equal") 120 | } 121 | } 122 | 123 | func TestCCNot(t *testing.T) { 124 | for i := 0; i < 8; i++ { 125 | s := NewSimulationBits(3, uint(i)) 126 | output := i ^ (((i & 1) << 2) & ((i & 2) << 1)) 127 | CCNot(s, 0, 1, 2) 128 | if cmplx.Abs(s.Phases[output]-1) > 1e-8 { 129 | t.Error("incorrect result for", i) 130 | } 131 | } 132 | } 133 | 134 | func TestSimulationInterference(t *testing.T) { 135 | for i := 0; i < 4; i++ { 136 | s := NewSimulationBits(2, uint(i)) 137 | H(s, 0) 138 | H(s, 1) 139 | s.CNot(0, 1) 140 | H(s, 0) 141 | H(s, 1) 142 | output := i ^ ((i & 2) >> 1) 143 | if cmplx.Abs(s.Phases[output]-1) > 1e-8 { 144 | t.Error("unexpected output for", i) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /quantum/cond.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | ) 7 | 8 | // Cond runs a function that only has an effect if a given 9 | // control bit is set. The function should not attempt to 10 | // modify the control bit. 11 | func Cond(c Computer, control int, f func(c Computer)) { 12 | f(&CondComputer{Computer: c, Control: control}) 13 | } 14 | 15 | // A CondComputer is a Computer that applies gates 16 | // conditioned on some qubit being set. Under the hood it 17 | // changes CNot gates to Toffoli gates, and Unitary gates 18 | // to controlled unitary gates. 19 | type CondComputer struct { 20 | Computer Computer 21 | Control int 22 | } 23 | 24 | func (c *CondComputer) NumBits() int { 25 | return c.Computer.NumBits() 26 | } 27 | 28 | func (c *CondComputer) InUse(bit int) bool { 29 | return bit == c.Control || c.Computer.InUse(bit) 30 | 31 | } 32 | 33 | func (c *CondComputer) Measure(bitIdx int) bool { 34 | return c.Computer.Measure(bitIdx) 35 | } 36 | 37 | func (c *CondComputer) Unitary(target int, m *Matrix2) { 38 | if target == c.Control { 39 | panic("cannot change control bit") 40 | } 41 | CUnitary(c.Computer, c.Control, target, m) 42 | } 43 | 44 | func (c *CondComputer) CNot(control, target int) { 45 | if target == c.Control { 46 | panic("cannot change control bit") 47 | } 48 | if control == c.Control { 49 | c.Computer.CNot(control, target) 50 | } else { 51 | CCNot(c.Computer, c.Control, control, target) 52 | } 53 | } 54 | 55 | // CUnitary applies the unitary matrix m to the target 56 | // qubit if the control qubit is set. 57 | // 58 | // The underlying implementation uses four unitaries and 59 | // two CNot gates. 60 | func CUnitary(c Computer, control, target int, m *Matrix2) { 61 | if m.M12 == 0 && m.M21 == 0 { 62 | if m.M11 == m.M22 { 63 | c.Unitary(control, &Matrix2{1, 0, 0, m.M11}) 64 | } else if m.M11 == 1 { 65 | sqrt := *m 66 | sqrt.M22 = cmplx.Sqrt(sqrt.M22) 67 | sqrtInv := sqrt 68 | sqrtInv.M22 = cmplx.Conj(sqrtInv.M22) 69 | c.Unitary(control, &sqrt) 70 | c.CNot(control, target) 71 | c.Unitary(target, &sqrtInv) 72 | c.CNot(control, target) 73 | c.Unitary(target, &sqrt) 74 | } else { 75 | phase := m.M11 76 | CUnitary(c, control, target, &Matrix2{phase, 0, 0, phase}) 77 | CUnitary(c, control, target, &Matrix2{1, 0, 0, m.M22 / phase}) 78 | } 79 | return 80 | } else if m.M22 == 0 && m.M11 == 0 { 81 | CUnitary(c, control, target, &Matrix2{m.M21, 0, 0, m.M12}) 82 | c.CNot(control, target) 83 | return 84 | } 85 | 86 | // https://arxiv.org/abs/quant-ph/9503016 87 | 88 | theta := 2 * math.Atan2(cmplx.Abs(m.M12), cmplx.Abs(m.M11)) 89 | 90 | a := imag(cmplx.Log(m.M11 / -m.M21)) 91 | var b, delta float64 92 | if cmplx.Abs(m.M11) > cmplx.Abs(m.M21) { 93 | b = imag(cmplx.Log((m.M11 / m.M22) * cmplx.Exp(complex(0, -a)))) 94 | delta = imag(cmplx.Log(m.M11 / cmplx.Exp(complex(0, a/2+b/2)))) 95 | } else { 96 | b = imag(cmplx.Log((-m.M21 / m.M12) * cmplx.Exp(complex(0, a)))) 97 | delta = imag(cmplx.Log(m.M12 / cmplx.Exp(complex(0, a/2-b/2)))) 98 | } 99 | 100 | mat1 := rotateZ(a) 101 | mat1A := rotateY(theta / 2) 102 | mat1.Mul(&mat1A) 103 | 104 | mat2 := rotateY(-theta / 2) 105 | mat2A := rotateZ(-(a + b) / 2) 106 | mat2.Mul(&mat2A) 107 | 108 | mat3 := rotateZ((b - a) / 2) 109 | mat4 := phaseShift(delta) 110 | 111 | c.Unitary(target, &mat3) 112 | c.CNot(control, target) 113 | c.Unitary(target, &mat2) 114 | c.CNot(control, target) 115 | c.Unitary(target, &mat1) 116 | CUnitary(c, control, target, &mat4) 117 | } 118 | 119 | func rotateY(theta float64) Matrix2 { 120 | cos := complex(math.Cos(theta/2), 0) 121 | sin := complex(math.Sin(theta/2), 0) 122 | return Matrix2{cos, sin, -sin, cos} 123 | } 124 | 125 | func rotateZ(alpha float64) Matrix2 { 126 | num := cmplx.Exp(complex(0, alpha/2)) 127 | return Matrix2{num, 0, 0, cmplx.Conj(num)} 128 | } 129 | 130 | func phaseShift(delta float64) Matrix2 { 131 | num := cmplx.Exp(complex(0, delta)) 132 | return Matrix2{num, 0, 0, num} 133 | } 134 | -------------------------------------------------------------------------------- /quantum/cond_test.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | func TestCUnitary(t *testing.T) { 11 | testMat := func(t *testing.T, m *Matrix2) { 12 | s1 := RandomSimulation(3) 13 | s2 := s1.Copy() 14 | rawCUnitary(s1, 2, 1, m) 15 | CUnitary(s2, 2, 1, m) 16 | if !s1.ApproxEqual(s2, 1e-8) { 17 | t.Fatal("incorrect result") 18 | } 19 | } 20 | t.Run("Diagonal", func(t *testing.T) { 21 | for i := 0; i < 100; i++ { 22 | mat := Matrix2{cmplx.Exp(complex(0, rand.Float64()*2*math.Pi)), 0, 0, 23 | cmplx.Exp(complex(0, rand.Float64()*2*math.Pi))} 24 | testMat(t, &mat) 25 | } 26 | }) 27 | t.Run("OffDiagonal", func(t *testing.T) { 28 | for i := 0; i < 100; i++ { 29 | mat := Matrix2{0, cmplx.Exp(complex(0, rand.Float64()*2*math.Pi)), 30 | cmplx.Exp(complex(0, rand.Float64()*2*math.Pi)), 0} 31 | testMat(t, &mat) 32 | } 33 | }) 34 | t.Run("Random", func(t *testing.T) { 35 | for i := 0; i < 1000; i++ { 36 | mat := RandomMatrix2() 37 | testMat(t, &mat) 38 | } 39 | }) 40 | t.Run("NumericalErrors", func(t *testing.T) { 41 | num := complex(1/math.Sqrt2, 0) 42 | h := Matrix2{num, num, num, -num} 43 | T := Matrix2{1, 0, 0, cmplx.Exp(complex(0, math.Pi/4))} 44 | invT := Matrix2{1, 0, 0, cmplx.Exp(complex(0, -math.Pi/4))} 45 | 46 | // Create an _almost_ diagonal matrix with some 47 | // off-diagonal terms on the order of 1e-17. 48 | res := T 49 | res.Mul(&h) 50 | res.Mul(&T) 51 | res.Mul(&h) 52 | res.Mul(&T) 53 | res.Mul(&invT) 54 | res.Mul(&h) 55 | res.Mul(&invT) 56 | res.Mul(&h) 57 | 58 | testMat(t, &res) 59 | 60 | // Swap the rows and try again. 61 | res.M11, res.M12, res.M21, res.M22 = res.M21, res.M22, res.M11, res.M12 62 | testMat(t, &res) 63 | }) 64 | } 65 | 66 | func rawCUnitary(s *Simulation, control, target int, m *Matrix2) { 67 | for i := range s.Phases { 68 | if i&(1<= 0; i-- { 50 | res = append(res, c[i].Inverse()) 51 | } 52 | return res 53 | } 54 | 55 | type HGate struct { 56 | Bit int 57 | } 58 | 59 | func (h *HGate) String() string { 60 | return "H(" + strconv.Itoa(h.Bit) + ")" 61 | } 62 | 63 | func (h *HGate) Apply(c Computer) { 64 | H(c, h.Bit) 65 | } 66 | 67 | func (h *HGate) Inverse() Gate { 68 | return h 69 | } 70 | 71 | type CHGate struct { 72 | Control int 73 | Target int 74 | } 75 | 76 | func (c *CHGate) String() string { 77 | return fmt.Sprintf("CH(%d, %d)", c.Control, c.Target) 78 | } 79 | 80 | func (c *CHGate) Apply(comp Computer) { 81 | CH(comp, c.Control, c.Target) 82 | } 83 | 84 | func (c *CHGate) Inverse() Gate { 85 | return c 86 | } 87 | 88 | type TGate struct { 89 | Bit int 90 | Conjugate bool 91 | } 92 | 93 | func (t *TGate) String() string { 94 | conjStr := "" 95 | if t.Conjugate { 96 | conjStr = "*" 97 | } 98 | return "T" + conjStr + "(" + strconv.Itoa(t.Bit) + ")" 99 | } 100 | 101 | func (t *TGate) Apply(c Computer) { 102 | if t.Conjugate { 103 | InvT(c, t.Bit) 104 | } else { 105 | T(c, t.Bit) 106 | } 107 | } 108 | 109 | func (t *TGate) Inverse() Gate { 110 | return &TGate{ 111 | Bit: t.Bit, 112 | Conjugate: !t.Conjugate, 113 | } 114 | } 115 | 116 | type XGate struct { 117 | Bit int 118 | } 119 | 120 | func (x *XGate) String() string { 121 | return "X(" + strconv.Itoa(x.Bit) + ")" 122 | } 123 | 124 | func (x *XGate) Apply(c Computer) { 125 | X(c, x.Bit) 126 | } 127 | 128 | func (x *XGate) Inverse() Gate { 129 | return x 130 | } 131 | 132 | type YGate struct { 133 | Bit int 134 | } 135 | 136 | func (y *YGate) String() string { 137 | return "Y(" + strconv.Itoa(y.Bit) + ")" 138 | } 139 | 140 | func (y *YGate) Apply(c Computer) { 141 | Y(c, y.Bit) 142 | } 143 | 144 | func (y *YGate) Inverse() Gate { 145 | return y 146 | } 147 | 148 | type ZGate struct { 149 | Bit int 150 | } 151 | 152 | func (z *ZGate) String() string { 153 | return "Z(" + strconv.Itoa(z.Bit) + ")" 154 | } 155 | 156 | func (z *ZGate) Apply(c Computer) { 157 | Z(c, z.Bit) 158 | } 159 | 160 | func (z *ZGate) Inverse() Gate { 161 | return z 162 | } 163 | 164 | type CNotGate struct { 165 | Control int 166 | Target int 167 | } 168 | 169 | func (c *CNotGate) String() string { 170 | return fmt.Sprintf("CNot(%d, %d)", c.Control, c.Target) 171 | } 172 | 173 | func (c *CNotGate) Apply(comp Computer) { 174 | comp.CNot(c.Control, c.Target) 175 | } 176 | 177 | func (c *CNotGate) Inverse() Gate { 178 | return c 179 | } 180 | 181 | type CCNotGate struct { 182 | Control1 int 183 | Control2 int 184 | Target int 185 | } 186 | 187 | func (c *CCNotGate) String() string { 188 | return fmt.Sprintf("CCNot(%d, %d, %d)", c.Control1, c.Control2, c.Target) 189 | } 190 | 191 | func (c *CCNotGate) Apply(comp Computer) { 192 | CCNot(comp, c.Control1, c.Control2, c.Target) 193 | } 194 | 195 | func (c *CCNotGate) Inverse() Gate { 196 | return c 197 | } 198 | 199 | type SqrtNotGate struct { 200 | Bit int 201 | Invert bool 202 | } 203 | 204 | func (s *SqrtNotGate) String() string { 205 | if s.Invert { 206 | return "SqrtNot*(" + strconv.Itoa(s.Bit) + ")" 207 | } else { 208 | return "SqrtNot(" + strconv.Itoa(s.Bit) + ")" 209 | } 210 | } 211 | 212 | func (s *SqrtNotGate) Apply(c Computer) { 213 | if s.Invert { 214 | InvSqrtNot(c, s.Bit) 215 | } else { 216 | SqrtNot(c, s.Bit) 217 | } 218 | } 219 | 220 | func (s *SqrtNotGate) Inverse() Gate { 221 | return &SqrtNotGate{ 222 | Bit: s.Bit, 223 | Invert: !s.Invert, 224 | } 225 | } 226 | 227 | type CSqrtNotGate struct { 228 | Control int 229 | Target int 230 | Invert bool 231 | } 232 | 233 | func (c *CSqrtNotGate) String() string { 234 | if c.Invert { 235 | return fmt.Sprintf("CSqrtNot*(%d, %d)", c.Control, c.Target) 236 | } else { 237 | return fmt.Sprintf("CSqrtNot(%d, %d)", c.Control, c.Target) 238 | } 239 | } 240 | 241 | func (c *CSqrtNotGate) Apply(comp Computer) { 242 | if c.Invert { 243 | InvCSqrtNot(comp, c.Control, c.Target) 244 | } else { 245 | CSqrtNot(comp, c.Control, c.Target) 246 | } 247 | } 248 | 249 | func (c *CSqrtNotGate) Inverse() Gate { 250 | return &CSqrtNotGate{ 251 | Control: c.Control, 252 | Target: c.Target, 253 | Invert: !c.Invert, 254 | } 255 | } 256 | 257 | type SqrtTGate struct { 258 | Bit int 259 | Conjugate bool 260 | } 261 | 262 | func (s *SqrtTGate) String() string { 263 | conjStr := "" 264 | if s.Conjugate { 265 | conjStr = "*" 266 | } 267 | return "SqrtT" + conjStr + "(" + strconv.Itoa(s.Bit) + ")" 268 | } 269 | 270 | func (s *SqrtTGate) Apply(c Computer) { 271 | if s.Conjugate { 272 | InvSqrtT(c, s.Bit) 273 | } else { 274 | SqrtT(c, s.Bit) 275 | } 276 | } 277 | 278 | func (s *SqrtTGate) Inverse() Gate { 279 | return &SqrtTGate{Bit: s.Bit, Conjugate: !s.Conjugate} 280 | } 281 | 282 | type SwapGate struct { 283 | A int 284 | B int 285 | } 286 | 287 | func (s *SwapGate) String() string { 288 | return fmt.Sprintf("Swap(%d, %d)", s.A, s.B) 289 | } 290 | 291 | func (s *SwapGate) Apply(c Computer) { 292 | Swap(c, s.A, s.B) 293 | } 294 | 295 | func (s *SwapGate) Inverse() Gate { 296 | return s 297 | } 298 | 299 | type CSwapGate struct { 300 | Control int 301 | A int 302 | B int 303 | } 304 | 305 | func (c *CSwapGate) String() string { 306 | return fmt.Sprintf("CSwap(%d, %d, %d)", c.Control, c.A, c.B) 307 | } 308 | 309 | func (c *CSwapGate) Apply(comp Computer) { 310 | CSwap(comp, c.Control, c.A, c.B) 311 | } 312 | 313 | func (c *CSwapGate) Inverse() Gate { 314 | return c 315 | } 316 | 317 | // FnGate is a gate that calls contained functions. 318 | type FnGate struct { 319 | Forward func(c Computer) 320 | Backward func(c Computer) 321 | Str string 322 | } 323 | 324 | func (f *FnGate) String() string { 325 | return f.Str 326 | } 327 | 328 | func (f *FnGate) Apply(c Computer) { 329 | f.Forward(c) 330 | } 331 | 332 | func (f *FnGate) Inverse() Gate { 333 | return &FnGate{Forward: f.Backward, Backward: f.Forward, Str: "Inv(" + f.Str + ")"} 334 | } 335 | 336 | // A ClassicalGate applies a bitwise function to classical 337 | // bases states. 338 | // It can only be applied to *Simulator computers. 339 | type ClassicalGate struct { 340 | F func(b []bool) []bool 341 | Inverted bool 342 | Str string 343 | } 344 | 345 | func NewClassicalGate(F func(b []bool) []bool, str string) *ClassicalGate { 346 | if str == "" { 347 | str = "ClassicalGate" 348 | } 349 | return &ClassicalGate{ 350 | F: F, 351 | Str: str, 352 | } 353 | } 354 | 355 | func (c *ClassicalGate) Apply(qc Computer) { 356 | s := qc.(*Simulation) 357 | s1 := s.Copy() 358 | if c.Inverted { 359 | for i := range s1.Phases { 360 | input := make([]bool, s.NumBits()) 361 | for j := range input { 362 | input[j] = (i&(1<>24), byte(r>>16), byte(r>>8), byte(r), 48 | byte(im>>24), byte(im>>16), byte(im>>8), byte(im)) 49 | } 50 | return md5.Sum(data) 51 | } 52 | 53 | func (c *circuitHasher) Prefix(g Applier) CircuitHasher { 54 | s := c.startState.Copy() 55 | g.Apply(s) 56 | return &circuitHasher{startState: s} 57 | } 58 | 59 | type symHasher struct { 60 | startState *Simulation 61 | } 62 | 63 | // NewSymHasher creates a CircuitHasher that yields equal 64 | // hashes for circuits that are equivalent up to a 65 | // permutation of the qubits. 66 | // 67 | // There may be false collisions, as the SymHasher is just 68 | // a heuristic. For example, the conditional swap gate 69 | // will yield a hash equivalent to the identity. 70 | func NewSymHasher(numBits int) CircuitHasher { 71 | coefficients := make([]complex128, numBits+1) 72 | for i := range coefficients { 73 | coefficients[i] = complex(rand.Float64(), rand.Float64()) 74 | } 75 | 76 | state := NewSimulation(numBits) 77 | for i := range state.Phases { 78 | state.Phases[i] = coefficients[countOnes(numBits, i)] 79 | } 80 | 81 | var mag float64 82 | for _, p := range state.Phases { 83 | mag += math.Pow(cmplx.Abs(p), 2) 84 | } 85 | normalizer := complex(1/math.Sqrt(mag), 0) 86 | for i := range state.Phases { 87 | state.Phases[i] *= normalizer 88 | } 89 | 90 | return &symHasher{startState: state} 91 | } 92 | 93 | func (s *symHasher) NumBits() int { 94 | return s.startState.NumBits() 95 | } 96 | 97 | func (s *symHasher) Hash(g Applier) CircuitHash { 98 | sim := s.startState.Copy() 99 | g.Apply(sim) 100 | 101 | bitSums := make([]uint64, sim.NumBits()+1) 102 | for i, phase := range sim.Phases { 103 | r := uint64(uint32(int32(math.Round(valueScale * real(phase))))) 104 | im := uint64(uint32(int32(math.Round(valueScale * imag(phase))))) 105 | 106 | enc := r | (im << 32) 107 | bitSums[countOnes(sim.NumBits(), i)] += enc 108 | } 109 | 110 | data := make([]byte, len(bitSums)*8) 111 | for i, x := range bitSums { 112 | for j := 0; j < 8; j++ { 113 | data[i*8+j] = byte(x >> uint(j*8)) 114 | } 115 | } 116 | 117 | return md5.Sum(data) 118 | } 119 | 120 | func (s *symHasher) Prefix(g Applier) CircuitHasher { 121 | sim := s.startState.Copy() 122 | g.Apply(sim) 123 | return &symHasher{startState: sim} 124 | } 125 | 126 | func invPermuteBits(perm []int, num int) int { 127 | var res int 128 | for i, target := range perm { 129 | if num&(1< 1e-8 || cmplx.Abs(m.M22-1) > 1e-8 { 15 | t.Error("invalid diagonal", m.M11, m.M22) 16 | } 17 | if cmplx.Abs(m.M12) > 1e-8 || cmplx.Abs(m.M21) > 1e-8 { 18 | t.Error("invalid off-diagonal", m.M12, m.M21) 19 | } 20 | } 21 | } 22 | 23 | func TestMatrix2Sqrt(t *testing.T) { 24 | m := RandomMatrix2() 25 | s := m 26 | s.Sqrt() 27 | s.Mul(&s) 28 | 29 | if cmplx.Abs(m.M11-s.M11) > 1e-8 || cmplx.Abs(m.M12-s.M12) > 1e-8 || 30 | cmplx.Abs(m.M21-s.M21) > 1e-8 || cmplx.Abs(m.M22-s.M22) > 1e-8 { 31 | t.Error("incorrect square") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum/primitives.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | ) 7 | 8 | var ( 9 | tGateValue = cmplx.Exp(complex(0, math.Pi/4)) 10 | invTGateValue = cmplx.Exp(complex(0, -math.Pi/4)) 11 | ) 12 | 13 | // H performs a Hadamard gate. 14 | func H(c Computer, bitIdx int) { 15 | s := complex(1/math.Sqrt2, 0) 16 | c.Unitary(bitIdx, &Matrix2{s, s, s, -s}) 17 | } 18 | 19 | // T performs a rotation by pi/4. 20 | func T(c Computer, bitIdx int) { 21 | c.Unitary(bitIdx, &Matrix2{1, 0, 0, tGateValue}) 22 | } 23 | 24 | // InvT performs an inverse rotation by pi/4. 25 | func InvT(c Computer, bitIdx int) { 26 | c.Unitary(bitIdx, &Matrix2{1, 0, 0, invTGateValue}) 27 | } 28 | 29 | // SqrtT performs the positive square root of the T gate. 30 | func SqrtT(c Computer, bitIdx int) { 31 | c.Unitary(bitIdx, &Matrix2{1, 0, 0, cmplx.Exp(complex(0, math.Pi/8))}) 32 | } 33 | 34 | // InvSqrtT performs the negative square root of the T gate. 35 | func InvSqrtT(c Computer, bitIdx int) { 36 | c.Unitary(bitIdx, &Matrix2{1, 0, 0, cmplx.Exp(complex(0, -math.Pi/8))}) 37 | } 38 | 39 | // X performs a not gate. 40 | func X(c Computer, bitIdx int) { 41 | c.Unitary(bitIdx, &Matrix2{0, 1, 1, 0}) 42 | } 43 | 44 | // Y performs a Pauli Y-gate. 45 | func Y(c Computer, bitIdx int) { 46 | c.Unitary(bitIdx, &Matrix2{0, complex(0, -1), complex(0, 1), 0}) 47 | } 48 | 49 | // Z performs a Pauli Z-gate. 50 | func Z(c Computer, bitIdx int) { 51 | c.Unitary(bitIdx, &Matrix2{1, 0, 0, -1}) 52 | } 53 | 54 | // SqrtNot performs the square root of the Not gate. 55 | func SqrtNot(c Computer, bitIdx int) { 56 | H(c, bitIdx) 57 | InvT(c, bitIdx) 58 | InvT(c, bitIdx) 59 | H(c, bitIdx) 60 | } 61 | 62 | // InvSqrtNot performs the inverse of SqrtNot. 63 | func InvSqrtNot(c Computer, bitIdx int) { 64 | H(c, bitIdx) 65 | T(c, bitIdx) 66 | T(c, bitIdx) 67 | H(c, bitIdx) 68 | } 69 | 70 | // CSqrtNot performs a controlled SqrtNot gate. 71 | func CSqrtNot(c Computer, control, target int) { 72 | // Found via search. 73 | H(c, target) 74 | InvT(c, control) 75 | c.CNot(control, target) 76 | T(c, target) 77 | c.CNot(control, target) 78 | InvT(c, target) 79 | H(c, target) 80 | } 81 | 82 | // InvCSqrtNot is the inverse of CSqrtNot. 83 | func InvCSqrtNot(c Computer, control, target int) { 84 | H(c, target) 85 | T(c, target) 86 | c.CNot(control, target) 87 | InvT(c, target) 88 | c.CNot(control, target) 89 | T(c, control) 90 | H(c, target) 91 | } 92 | 93 | // Swap swaps two qubits. 94 | func Swap(c Computer, a, b int) { 95 | c.CNot(a, b) 96 | c.CNot(b, a) 97 | c.CNot(a, b) 98 | } 99 | 100 | // CSwap swaps two qubits conditioned on a control qubit. 101 | func CSwap(c Computer, control, a, b int) { 102 | // Found via search. 103 | c.CNot(a, b) 104 | H(c, a) 105 | c.CNot(control, a) 106 | T(c, control) 107 | InvT(c, a) 108 | c.CNot(b, a) 109 | T(c, a) 110 | c.CNot(control, a) 111 | c.CNot(b, control) 112 | InvT(c, control) 113 | InvT(c, a) 114 | c.CNot(b, a) 115 | c.CNot(b, control) 116 | T(c, b) 117 | T(c, a) 118 | H(c, a) 119 | c.CNot(a, b) 120 | } 121 | 122 | // SqrtSwap perfroms the square root of the Swap gate. 123 | func SqrtSwap(c Computer, a, b int) { 124 | // Found via search. 125 | InvT(c, a) 126 | InvT(c, a) 127 | c.CNot(a, b) 128 | H(c, a) 129 | InvT(c, b) 130 | c.CNot(a, b) 131 | T(c, b) 132 | T(c, b) 133 | } 134 | 135 | // InvSqrtSwap perfroms the inverse of SqrtSwap. 136 | func InvSqrtSwap(c Computer, a, b int) { 137 | InvT(c, b) 138 | InvT(c, b) 139 | c.CNot(a, b) 140 | T(c, b) 141 | H(c, a) 142 | c.CNot(a, b) 143 | T(c, a) 144 | T(c, a) 145 | } 146 | 147 | // CH applies a controlled Hadamard gate. 148 | func CH(c Computer, control, target int) { 149 | // Found via search. 150 | T(c, target) 151 | T(c, target) 152 | H(c, target) 153 | T(c, target) 154 | c.CNot(control, target) 155 | InvT(c, target) 156 | H(c, target) 157 | InvT(c, target) 158 | InvT(c, target) 159 | } 160 | -------------------------------------------------------------------------------- /quantum/primitives_test.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/cmplx" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkUnitary(b *testing.B) { 11 | for _, size := range []int{1, 5, 10} { 12 | b.Run(fmt.Sprintf("Bits%d", size), func(b *testing.B) { 13 | s := RandomSimulation(size) 14 | coeff := complex(1.0/math.Sqrt2, 0) 15 | for i := 0; i < b.N; i++ { 16 | s.Unitary(i%size, &Matrix2{coeff, coeff, coeff, -coeff}) 17 | } 18 | }) 19 | } 20 | } 21 | 22 | func BenchmarkT(b *testing.B) { 23 | for _, size := range []int{1, 5, 10} { 24 | b.Run(fmt.Sprintf("Bits%d", size), func(b *testing.B) { 25 | s := RandomSimulation(size) 26 | for i := 0; i < b.N; i++ { 27 | T(s, i%size) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func BenchmarkCNot(b *testing.B) { 34 | for _, size := range []int{2, 5, 10} { 35 | b.Run(fmt.Sprintf("Bits%d", size), func(b *testing.B) { 36 | s := RandomSimulation(size) 37 | for i := 0; i < b.N; i++ { 38 | s.CNot(i%size, (i+1)%size) 39 | } 40 | }) 41 | } 42 | } 43 | 44 | func TestCSqrtNot(t *testing.T) { 45 | testControl(t, SqrtNot, InvSqrtNot, CSqrtNot, InvCSqrtNot) 46 | } 47 | 48 | func TestCH(t *testing.T) { 49 | testControl(t, H, H, CH, CH) 50 | } 51 | 52 | func TestSqrtSwap(t *testing.T) { 53 | for i := 0; i < 4; i++ { 54 | s := NewSimulationBits(2, uint(i)) 55 | SqrtSwap(s, 0, 1) 56 | SqrtSwap(s, 0, 1) 57 | Swap(s, 0, 1) 58 | if cmplx.Abs(s.Phases[i]-1) > 1e-8 { 59 | t.Error("invalid square") 60 | } 61 | 62 | SqrtSwap(s, 0, 1) 63 | InvSqrtSwap(s, 0, 1) 64 | if cmplx.Abs(s.Phases[i]-1) > 1e-8 { 65 | t.Error("invalid inverse") 66 | } 67 | } 68 | } 69 | 70 | func TestCSwap(t *testing.T) { 71 | g1 := &CSwapGate{1, 2, 3} 72 | g2 := &ClassicalGate{ 73 | F: func(b []bool) []bool { 74 | res := append([]bool{}, b...) 75 | if res[1] { 76 | res[2], res[3] = res[3], res[2] 77 | } 78 | return res 79 | }, 80 | } 81 | hasher := NewCircuitHasher(4) 82 | if hasher.Hash(g1) != hasher.Hash(g2) { 83 | t.Error("invalid circuit") 84 | } 85 | } 86 | 87 | func testControl(t *testing.T, fwd func(c Computer, bit int), inv func(c Computer, bit int), 88 | controlled func(c Computer, ctrl, targ int), 89 | controlledInv func(c Computer, ctrl, targ int)) { 90 | rawGate := &FnGate{ 91 | Forward: func(c Computer) { 92 | fwd(c, 2) 93 | }, 94 | Backward: func(c Computer) { 95 | inv(c, 2) 96 | }, 97 | } 98 | idealGate := &FnGate{ 99 | Forward: func(c Computer) { 100 | c.(*Simulation).ControlGate(0, rawGate) 101 | }, 102 | Backward: func(c Computer) { 103 | c.(*Simulation).ControlGate(0, rawGate.Inverse()) 104 | }, 105 | } 106 | actualGate := &FnGate{ 107 | Forward: func(c Computer) { 108 | controlled(c, 0, 2) 109 | }, 110 | Backward: func(c Computer) { 111 | controlledInv(c, 0, 2) 112 | }, 113 | } 114 | 115 | hasher := NewCircuitHasher(3) 116 | hash1 := hasher.Hash(idealGate) 117 | hash2 := hasher.Hash(actualGate) 118 | if hash1 != hash2 { 119 | t.Error("invalid forward hash") 120 | } 121 | 122 | hash1 = hasher.Hash(idealGate.Inverse()) 123 | hash2 = hasher.Hash(actualGate.Inverse()) 124 | if hash1 != hash2 { 125 | t.Error("invalid backward hash") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /quantum/reg.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | // A Reg is a "quantum register". It is simply a list of 4 | // distinct qubit indices. 5 | type Reg []int 6 | 7 | // Valid ensures that no qubits are repeated in the list 8 | // and that no qubit indices are less than 0. 9 | func (r Reg) Valid() bool { 10 | set := map[int]bool{} 11 | for _, x := range r { 12 | if set[x] || x < 0 { 13 | return false 14 | } 15 | set[x] = true 16 | } 17 | return true 18 | } 19 | 20 | // Overlaps checks if r shares any bits in common with r1. 21 | func (r Reg) Overlaps(r1 Reg) bool { 22 | for _, x := range r { 23 | for _, y := range r1 { 24 | if x == y { 25 | return true 26 | } 27 | } 28 | } 29 | return false 30 | } 31 | 32 | // Extract extracts the register from a computer state. 33 | // The bits in the computer state are stored lowest to 34 | // highest, as are the bits in the register. 35 | func (r Reg) Extract(state uint) uint { 36 | var res uint 37 | for i, x := range r { 38 | if state&(1< 0 { 37 | c.extendCache() 38 | } 39 | if numGates >= len(c.cache) { 40 | return nil 41 | } 42 | return c.cache[numGates] 43 | } 44 | 45 | // Generate generates a (possibly redundant) sequence of 46 | // circuits of a given size. 47 | func (c *CircuitGen) Generate(numGates int) (<-chan Circuit, int) { 48 | for len(c.cache) <= numGates && c.cacheRemaining > 0 { 49 | c.extendCache() 50 | } 51 | 52 | ch := make(chan Circuit, 10) 53 | 54 | if numGates < len(c.cache) { 55 | go func() { 56 | defer close(ch) 57 | for _, circ := range c.cache[numGates] { 58 | ch <- circ 59 | } 60 | }() 61 | return ch, len(c.cache[numGates]) 62 | } 63 | 64 | subCh, subCount := c.Generate(numGates - len(c.cache) + 1) 65 | go func() { 66 | defer close(ch) 67 | for subCirc := range subCh { 68 | for _, circ := range c.cache[len(c.cache)-1] { 69 | ch <- append(append(Circuit{}, subCirc...), circ...) 70 | } 71 | } 72 | }() 73 | 74 | return ch, subCount * len(c.cache[len(c.cache)-1]) 75 | } 76 | 77 | func (c *CircuitGen) extendCache() bool { 78 | var next []Circuit 79 | found := map[CircuitHash]bool{} 80 | for _, prevCirc := range c.cache[len(c.cache)-1] { 81 | for _, gate := range c.basis { 82 | circ := append(Circuit{gate}, prevCirc...) 83 | hash := c.hasher.Hash(circ) 84 | if !found[hash] { 85 | found[hash] = true 86 | next = append(next, circ) 87 | c.cacheRemaining -= 1 88 | if c.cacheRemaining == 0 { 89 | return false 90 | } 91 | } 92 | } 93 | } 94 | c.cache = append(c.cache, next) 95 | return true 96 | } 97 | 98 | // A BackwardsMap keeps track of the latter half of 99 | // circuits for a bidirectional search. 100 | type BackwardsMap struct { 101 | hasher CircuitHasher 102 | backHasher CircuitHasher 103 | goal CircuitHash 104 | m map[CircuitHash]Gate 105 | } 106 | 107 | // NewBackwardsMap creates a BackwardsMap that operates 108 | // with respect to the end state achieved by a goal. 109 | // 110 | // It uses the hasher c for internal logic. 111 | func NewBackwardsMap(c CircuitHasher, goal Gate) *BackwardsMap { 112 | return &BackwardsMap{ 113 | hasher: c, 114 | backHasher: c.Prefix(goal), 115 | goal: c.Hash(goal), 116 | m: map[CircuitHash]Gate{}, 117 | } 118 | } 119 | 120 | // AddCircuit adds the circuit to the reverse mapping. 121 | // Circuits should be added in order of size. 122 | func (b *BackwardsMap) AddCircuit(c Circuit) { 123 | backHash := b.backHasher.Hash(c.Inverse()) 124 | if _, ok := b.m[backHash]; !ok { 125 | b.m[backHash] = c[0] 126 | } 127 | } 128 | 129 | // Lookup finds the shortest suffix to complete the given 130 | // prefix of the solution. 131 | // 132 | // If no circuit is found, nil is returned. 133 | func (b *BackwardsMap) Lookup(prefix Gate) Circuit { 134 | var res Circuit 135 | hasher := b.hasher.Prefix(prefix) 136 | h := hasher.Hash(Circuit{}) 137 | for h != b.goal { 138 | if g, ok := b.m[h]; !ok { 139 | return nil 140 | } else { 141 | res = append(res, g) 142 | hasher = hasher.Prefix(g) 143 | h = hasher.Hash(Circuit{}) 144 | } 145 | } 146 | return res 147 | } 148 | -------------------------------------------------------------------------------- /quantum/toffoli.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import "math" 4 | 5 | // CCNot performs a Toffoli gate. 6 | func CCNot(c Computer, control1, control2, target int) { 7 | // https://quantum.country/qcvc 8 | H(c, target) 9 | c.CNot(control2, target) 10 | InvT(c, target) 11 | c.CNot(control1, target) 12 | T(c, target) 13 | c.CNot(control2, target) 14 | InvT(c, target) 15 | c.CNot(control1, target) 16 | T(c, control2) 17 | T(c, target) 18 | H(c, target) 19 | c.CNot(control1, control2) 20 | T(c, control1) 21 | InvT(c, control2) 22 | c.CNot(control1, control2) 23 | } 24 | 25 | // ToffoliN performs an n-bit Toffoli gate. 26 | // 27 | // This typically requires that there is at least one 28 | // spare qubit on the computer. 29 | func ToffoliN(c Computer, target int, control ...int) { 30 | if len(control) == 0 { 31 | X(c, target) 32 | } else if len(control) == 1 { 33 | c.CNot(control[0], target) 34 | } else if len(control) == 2 { 35 | CCNot(c, control[0], control[1], target) 36 | } else { 37 | working := allocWorking(c, target, control...) 38 | if len(working) == 0 { 39 | panic("not enough working qubits") 40 | } else if len(working) >= len(control)-2 { 41 | // Lemma 7.2 from Barenco et al. 1995 42 | targets := append(append([]int{}, working[:len(control)-2]...), target) 43 | 44 | for i := len(control) - 1; i > 1; i-- { 45 | CCNot(c, control[i], targets[i-2], targets[i-1]) 46 | } 47 | CCNot(c, control[0], control[1], targets[0]) 48 | for i := 2; i < len(control); i++ { 49 | CCNot(c, control[i], targets[i-2], targets[i-1]) 50 | } 51 | 52 | // Undo side-effects 53 | for i := len(control) - 2; i > 1; i-- { 54 | CCNot(c, control[i], targets[i-2], targets[i-1]) 55 | } 56 | CCNot(c, control[0], control[1], targets[0]) 57 | for i := 2; i < len(control)-1; i++ { 58 | CCNot(c, control[i], targets[i-2], targets[i-1]) 59 | } 60 | } else { 61 | // Lemma 7.3 from Barenco et al. 1995 62 | size := int(math.Ceil(float64(len(control)+1) / 2)) 63 | control1 := control[:size] 64 | control2 := append([]int{working[0]}, control[size:]...) 65 | ToffoliN(c, working[0], control1...) 66 | ToffoliN(c, target, control2...) 67 | ToffoliN(c, working[0], control1...) 68 | ToffoliN(c, target, control2...) 69 | } 70 | } 71 | } 72 | 73 | // allocWorking finds the available working bits. 74 | func allocWorking(c Computer, a int, b ...int) []int { 75 | var res []int 76 | OuterLoop: 77 | for i := 0; i < c.NumBits(); i++ { 78 | if i == a || c.InUse(i) { 79 | continue 80 | } 81 | for _, x := range b { 82 | if i == x { 83 | continue OuterLoop 84 | } 85 | } 86 | res = append(res, i) 87 | } 88 | return res 89 | } 90 | -------------------------------------------------------------------------------- /quantum/toffoli_test.go: -------------------------------------------------------------------------------- 1 | package quantum 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/unixpickle/essentials" 8 | ) 9 | 10 | func TestToffoliN(t *testing.T) { 11 | for i := 0; i < 1000; i++ { 12 | numBits := rand.Intn(10) + 1 13 | target := rand.Intn(numBits) 14 | var numControl int 15 | if numBits <= 3 { 16 | numControl = rand.Intn(numBits) 17 | } else { 18 | numControl = rand.Intn(numBits - 2) 19 | } 20 | var control []int 21 | for j := 0; j < numControl; j++ { 22 | idx := rand.Intn(numBits) 23 | for idx == target || essentials.Contains(control, idx) { 24 | idx = rand.Intn(numBits) 25 | } 26 | control = append(control, idx) 27 | } 28 | s := RandomSimulation(numBits) 29 | expected := rawToffoliN(s, target, control) 30 | ToffoliN(s, target, control...) 31 | if expected.String() != s.String() { 32 | t.Errorf("error for %d bits with target %d and control %v", numBits, target, control) 33 | } 34 | } 35 | } 36 | 37 | func rawToffoliN(s *Simulation, target int, control []int) *Simulation { 38 | s1 := s.Copy() 39 | for i, phase := range s.Phases { 40 | matches := true 41 | for _, x := range control { 42 | if (i & (1 << uint(x))) == 0 { 43 | matches = false 44 | break 45 | } 46 | } 47 | if matches { 48 | s1.Phases[i^(1<