quantum_ruby 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 68be46a736a6bdd4f752c9232f65cddf546ca4dbf8b9b72f41fb6237860871e2
4
+ data.tar.gz: b0a51c8d0e885817db849e60f87a187e20a90dce5113d0e2c4a9b8962c6a2f85
5
+ SHA512:
6
+ metadata.gz: edbe804cdc48d763966e8b0f343aa8ab4d9dd00595f84b5951289311ca5f81623465b5ade7c5462b263b6402a4d81f4c86370a6360fd1e75db6b906462be1415
7
+ data.tar.gz: 72c0d4713f3025e9702c45aa05a9c56746e59f715322c769db7df1fe4a95c8b60bff536149e460500ad470e43a4e0fc0be27230dfabfe903a40996a1bf9f0658
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in quantum_ruby.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ quantum_ruby (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (12.3.3)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ bundler
16
+ quantum_ruby!
17
+ rake (~> 12.0)
18
+
19
+ BUNDLED WITH
20
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Alessandro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+
2
+ # QuantumRuby
3
+
4
+ This is a quantum computer simulator in under 300 lines of ruby. Meaning you can build arbitrarily large quantum circuits programmatically and simulate their behaviours.
5
+
6
+ <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Qcircuit_CNOTfromSQRTSWAP.svg/1024px-Qcircuit_CNOTfromSQRTSWAP.svg.png" />
7
+
8
+ Learning about quantum computing isn't that difficult. Trust me! I learned and built this simulator within five days with no prior quantum computing knowledge. This gem can also be a great tool to facilitate learning about quantum computing.
9
+
10
+ [Check out the many examples in this repo.](https://github.com/AlessandroMinali/quantum_ruby/examples)
11
+
12
+ ## How to learn about Quantum Computers
13
+ 1. Have an understanding of Linear Algebra. This [series of youtube lectures](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) can get you up to speed on everything you need to know
14
+ 2. Read this great intro to quantum computing. This is the single resource that I read before I began implementing this simulator: [Quantum computing for the very curious](https://quantum.country/qcvc)
15
+ 3. Experiment with this simulator as you learn about quantum computing
16
+ 4. Read the [other articles](https://quantum.country) about quantum computing. Read wiki pages about [qubits](https://en.wikipedia.org/wiki/Qubit) and [quantum gates](https://en.wikipedia.org/wiki/Quantum_logic_gate). Both should be understandable to you now!
17
+ 5. If you ever get stuck start back at the top of this list or reach out to me with questions: `alessandro.minali AT gmail DOT com`
18
+
19
+ ## Installation
20
+ Add this line to your application's Gemfile:
21
+ ```ruby
22
+ gem 'quantum_ruby'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle install
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install quantum_ruby
32
+ In your code:
33
+ ```ruby
34
+ require 'quantum_ruby`
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ **This gem adds `Matrix#kronecker` and `Complex#round` onto ruby base classes**
40
+
41
+ There are three objects that this gem uses to simulate any quantum circuitry.
42
+
43
+ ---
44
+ ### Qubit
45
+ Quantum computing is achieved by manipulating quantum bits(ie. qubits). For simulation purposes we can arbitrarily create qubits, manipulate them and read their results.
46
+
47
+ ```ruby
48
+ qubit_1 = Qubit.new(1, 0)
49
+ qubit_1.measure # returns 0 bit
50
+ qubit_2 = Qubit.new(0, 1)
51
+ qubit_2.measure # returns 1 bit
52
+ ```
53
+
54
+ ---
55
+
56
+ ### Gate
57
+ Quantum gates perform operations on single or multi qubits to produce a variety of classical and non-classical behaviours(such as superpositions and entanglement). Gates are simulated as matrices and interact with qubits via multiplication.
58
+
59
+ ```ruby
60
+ # single qubit gate example (X_GATE ie. quantum NOT gate)
61
+ qubit_1 = Qubit.new(0, 1) # equivalent to classical 1 bit
62
+ state = X_GATE * qubit_1
63
+ state.measure # returns 0 bit
64
+
65
+ # multi qubit gate example (C_NOT_GATE ie. controlled NOT gate)
66
+ control_qubit = Qubit.new(1, 0) # 0 bit
67
+ target_qubit = Qubit.new(0, 1) # 1 bit
68
+ # note multi params syntax: "GATE.*(param1, param2, etc.)"
69
+ state = C_NOT_GATE.*(control_qubit, target_qubit)
70
+ state.measure_partial(target_qubit) # return 1 bit since control is 0
71
+
72
+ # again with control bit ON
73
+ control_qubit = Qubit.new(0, 1) # 1 bit
74
+ target_qubit = Qubit.new(0, 1) # 1 bit
75
+ state = C_NOT_GATE.*(control_qubit, target_qubit)
76
+ state.measure_partial(target_qubit) # return 0 bit since control is 1
77
+ ```
78
+
79
+ #### Provided Gates
80
+ X_GATE
81
+ Y_GATE
82
+ Z_GATE
83
+ H_GATE
84
+ T_GATE
85
+ C_NOT_GATE
86
+ SWAP_GATE
87
+ TOFFOLI_GATE
88
+ Information about any of these gates behaviours can be found [here on wiki](https://en.wikipedia.org/wiki/Quantum_logic_gate).
89
+
90
+ #### Advanced Gate Usage
91
+
92
+ #### 1. Making your own gates
93
+ This can be done simply like creating a ruby `Matrix`:
94
+ ```ruby
95
+ x = Matrix[[0, 1], [1, 0]]
96
+ y = Gate[[0, 1], [1, 0]]
97
+ ```
98
+ Note: A true quantum gate must be [unitary](https://en.wikipedia.org/wiki/Unitary_matrix). You can verify if your creation is unitary with the built-in `Matrix#unitary?`
99
+ ###### [some versions of ruby have a broken implementation of this function](https://github.com/ruby/matrix/pull/14)
100
+ #### 2. `Matrix#kronecker(matrix)`
101
+ This method allows you to parallelize and scale gates. Examples follow:
102
+
103
+ Parallel Gates:
104
+ <img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/Parallel_quantum_logic_gates.png" />
105
+ ```ruby
106
+ Y_X_GATE = Y_GATE.kronecker(X_GATE)
107
+ ```
108
+
109
+ Scaling Gate:
110
+ <img src="https://upload.wikimedia.org/wikipedia/commons/d/d2/Shows_the_application_of_a_hadamard_gate_on_a_state_that_span_two_qubits.png" />
111
+ ```ruby
112
+ state = State.new(Matrix.column_vector([0, 0, 0, 1]))
113
+ # H_GATE normally only works on 1 qubit ie. 2x1 matrix
114
+ # the following will auto scale H_GATE with kronecker
115
+ new_state = H_GATE.*(state, scale: :down)
116
+ ```
117
+
118
+ ---
119
+
120
+ ### State
121
+
122
+ Generally once a qubit enters a circuit we no longer care about it. We are instead interested in the combine state of the whole circuit where multiple qubits are being processed by gates. After sending the qubits through a circuit this object will hold their results in a probabilistic model. We can take a measurement to get classical information back out of the qubits.
123
+
124
+ ```ruby
125
+ qubit_1 = Qubit.new(0, 1) # equivalent to classical 1 bit
126
+ state = H_GATE * qubit_1
127
+ state.measure # returns 0 bit or 1 bit with equal probability
128
+ ```
129
+
130
+ #### `State#measure` vs `State#measure_partial(qubits)`
131
+ In multi-qubit systems gates put the qubits into a combined state. We can either measure the entire state to determine an outcome or try to extract information about specific qubits at the expense of information of the total system. In most cases we are interested in `State#measure`. Make sure you understand which one you intend to do in any situation. [Resource](https://quantum.country/teleportation#background_partial_measurement)
132
+
133
+ ## Other Resources
134
+ For some trickier aspects of quantum computing these online resources really helped me grasp deeper concepts:
135
+ - https://quantumcomputing.stackexchange.com/questions/9614/how-to-interpret-a-4-qubit-quantum-circuit-as-a-matrix/9615#9615
136
+ - https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the-cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
137
+ - https://cs.stackexchange.com/questions/71462/how-are-partial-measurements-performed-on-a-n-qubit-quantum-circuit
138
+
139
+ ## Development
140
+
141
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
142
+
143
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
144
+
145
+ ## Contributing
146
+
147
+ Bug reports and pull requests are welcome on GitHub at https://github.com/AlessandroMinali/quantum_ruby.
148
+
149
+ ## License
150
+
151
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task default: :test
4
+
5
+ desc 'Run tests'
6
+ task :test do
7
+ sh 'ruby examples/*'
8
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "quantum_ruby"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,266 @@
1
+ require 'quantum_ruby'
2
+ # should be added in upcoming ruby/matrix patch
3
+ class Matrix
4
+ def adjoint
5
+ conjugate.transpose
6
+ end
7
+ end
8
+
9
+ # Matrix * test
10
+ a = Matrix[[1, 2, 3], [4, 5, 6]]
11
+ b = Matrix[[7, 8], [9, 10], [11, 12]]
12
+ raise unless a * b == Matrix[[58, 64], [139, 154]]
13
+
14
+ # Matrix * test
15
+ a = Matrix[[1, 2], [3, 4]]
16
+ b = Matrix[[2, 0], [1, 2]]
17
+ raise unless a * b == Matrix[[4, 4], [10, 8]]
18
+
19
+ # Kronecker product test
20
+ x = Matrix[[1, 2], [3, 4]]
21
+ y = Matrix[[0, 5], [6, 7]]
22
+ raise unless x.kronecker(y) == Matrix[[0, 5, 0, 10], [6, 7, 12, 14], [0, 15, 0, 20], [18, 21, 24, 28]]
23
+
24
+ x = Matrix[[1], [2]]
25
+ y = Matrix[[3], [4]]
26
+ raise unless x.kronecker(y) == Matrix[[3], [4], [6], [8]]
27
+
28
+ # 'Ket' visualization
29
+ x = Qubit.new(1, 0)
30
+ raise unless x.to_s == '1|0>'
31
+
32
+ # Complex number visualization
33
+ x = Qubit.new((1 + 1i) / 2, 1i / Math.sqrt(2))
34
+ raise unless x.to_s == '1/2+1/2i|0> + 0.707i|1>'
35
+ raise unless x.state == "[1/2+1/2i\n 0.707i]"
36
+
37
+ # NOT gate
38
+ z = Qubit.new(1, 0)
39
+ raise unless X_GATE * z == State.new(Matrix[[0], [1]])
40
+
41
+ # Uniform superposition gate
42
+ z = Qubit.new(1, 0)
43
+ raise unless H_GATE * z == State.new(Matrix[[1 / Math.sqrt(2)], [1 / Math.sqrt(2)]])
44
+
45
+ # Demonstrate gate reversibility
46
+ z = Qubit.new(0.6, 0.8)
47
+ raise unless H_GATE * H_GATE * z == State.new(Matrix[[0.6], [0.8]])
48
+
49
+ # Measuring
50
+ 1_000.times do
51
+ x = Qubit.new(1, 0).measure
52
+ raise unless x.zero?
53
+
54
+ x = Qubit.new(0, 1).measure
55
+ raise unless x == 1
56
+
57
+ # equal chance of result
58
+ s = (H_GATE * Qubit.new(0, 1))
59
+ case s.measure[0]
60
+ when 0
61
+ raise unless s == State.new(Matrix[[1], [0]])
62
+ when 1
63
+ raise unless s == State.new(Matrix[[0], [1]])
64
+ else
65
+ raise
66
+ end
67
+
68
+ x = Qubit.new(1, 0)
69
+ y = Qubit.new(0, 1)
70
+ a = Array.new(4, 0)
71
+ a[rand(4)] = 1
72
+ r = State.new(Matrix.column_vector(a), [x, y]).measure.join.to_i(2)
73
+ case r
74
+ when 0
75
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0)
76
+ when 1
77
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1)
78
+ when 2
79
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0)
80
+ when 3
81
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1)
82
+ end
83
+
84
+ x = Qubit.new(1, 0)
85
+ y = Qubit.new(0, 1)
86
+ z = Qubit.new(0, 1)
87
+ a = Array.new(8, 0)
88
+ a[rand(8)] = 1
89
+ r = State.new(Matrix.column_vector(a), [x, y, z]).measure.join.to_i(2)
90
+ case r
91
+ when 0
92
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
93
+ when 1
94
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
95
+ when 2
96
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
97
+ when 3
98
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
99
+ when 4
100
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
101
+ when 5
102
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
103
+ when 6
104
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
105
+ when 7
106
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
107
+ end
108
+ end
109
+
110
+ x = Qubit.new(1 / Math.sqrt(2), 1 / Math.sqrt(2))
111
+ raise unless (H_GATE * x).measure[0].zero?
112
+
113
+ x = Qubit.new(1 / Math.sqrt(2), -1 / Math.sqrt(2))
114
+ raise unless (H_GATE * x).measure[0] == 1
115
+
116
+ # Backwards control gate!
117
+ x = Qubit.new(1, 0)
118
+ y = Qubit.new(0, 1)
119
+ h_big = H_GATE.kronecker(H_GATE)
120
+ z = h_big * (C_NOT_GATE * h_big)
121
+ raise unless z.*(x, y) == State.new(Matrix[[0.0], [0.0], [0.0], [1.0]])
122
+
123
+ # Bell state
124
+ x = Qubit.new(1, 0)
125
+ y = Qubit.new(1, 0)
126
+ raise unless C_NOT_GATE.*(H_GATE * x, y) == State.new(Matrix[[0.7071067811865475], [0.0], [0.0], [0.7071067811865475]])
127
+
128
+ # Unitary inversion of gates
129
+ raise unless (C_NOT_GATE * H_GATE.kronecker(Matrix.identity(2))).adjoint == H_GATE.kronecker(Matrix.identity(2)) * C_NOT_GATE
130
+
131
+ # Auto scale gates
132
+ z = C_NOT_GATE.*(H_GATE * x, y)
133
+ raise unless H_GATE.*(z, scale: :down) == State.new(0.5 * Matrix[[1], [1], [1], [-1]])
134
+
135
+ # TOFFOLI_GATE test
136
+ raise unless TOFFOLI_GATE.*(Qubit.new(0, 1), Qubit.new(0, 1), Qubit.new(0, 1)) == State.new(Matrix.column_vector([0, 0, 0, 0, 0, 0, 1, 0]))
137
+
138
+ # Build a TOFFOLI_GATE from simple gates (C_NOT, H_GATE, and T_GATE's only)
139
+ T_3GATE_ADJOINT = I2.kronecker(I2).kronecker(T_GATE).adjoint
140
+ C_3NOT_GATE = Gate[*Matrix[[1, 0], [0, 0]].kronecker(I2.kronecker(I2)) + Matrix[[0, 0], [0, 1]].kronecker(I2.kronecker(X_GATE))]
141
+ raise unless TOFFOLI_GATE == C_NOT_GATE.*(T_GATE.kronecker(T_GATE.adjoint).*(C_NOT_GATE.kronecker(H_GATE).*(I2.kronecker(T_GATE).kronecker(T_GATE).*(C_3NOT_GATE.*(T_3GATE_ADJOINT.*(C_NOT_GATE.*(T_GATE.*(C_3NOT_GATE.*(T_3GATE_ADJOINT.*(C_NOT_GATE.*(I2.kronecker(I2).kronecker(H_GATE), scale: :up))), scale: :up), scale: :up))))), scale: :down), scale: :down).round
142
+
143
+ # Partial Measure
144
+ x = Qubit.new(0, 1)
145
+ y = Qubit.new(0, 1)
146
+ raise unless x == y
147
+
148
+ State.new(Matrix.column_vector([0, Math.sqrt(0.8), Math.sqrt(0.2), 0]), [x, y]).measure_partial(y)
149
+ raise if x == y
150
+
151
+ x = Qubit.new(0, 1)
152
+ y = Qubit.new(0, 1)
153
+ z = Qubit.new(0, 1)
154
+ State.new(Matrix.column_vector([0, 0.5, 0.5, 0, 0, 0.5, 0.5, 0]), [x, y, z]).measure_partial(y)
155
+ raise if (y == x) || (z == y)
156
+
157
+ 1_000.times do
158
+ x = Qubit.new(0, 1)
159
+ y = Qubit.new(0, 1)
160
+ z = Qubit.new(0, 1)
161
+ a = Array.new(8, 0)
162
+ a[rand(8)] = 1
163
+ r = State.new(Matrix.column_vector(a), [x, y, z]).measure_partial(y, z).join.to_i(2)
164
+ case r
165
+ when 0
166
+ raise unless y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
167
+ when 1
168
+ raise unless y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
169
+ when 2
170
+ raise unless y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
171
+ when 3
172
+ raise unless y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
173
+ else
174
+ raise
175
+ end
176
+
177
+ i = Qubit.new(0, 1)
178
+ y = Qubit.new(0, 1)
179
+ z = Qubit.new(0, 1)
180
+ a = Array.new(8, 0)
181
+ a[rand(8)] = 1
182
+ r = State.new(Matrix.column_vector(a), [y, z, i]).measure_partial(i, y).join.to_i(2)
183
+ case r
184
+ when 0
185
+ raise unless y == Qubit.new(1, 0) && i == Qubit.new(1, 0)
186
+ when 1
187
+ raise unless y == Qubit.new(1, 0) && i == Qubit.new(0, 1)
188
+ when 2
189
+ raise unless y == Qubit.new(0, 1) && i == Qubit.new(1, 0)
190
+ when 3
191
+ raise unless y == Qubit.new(0, 1) && i == Qubit.new(0, 1)
192
+ else
193
+ raise
194
+ end
195
+
196
+ x = Qubit.new(0, 1)
197
+ y = Qubit.new(0, 1)
198
+ z = Qubit.new(0, 1)
199
+ i = Qubit.new(0, 1)
200
+ a = Array.new(16, 0)
201
+ a[rand(16)] = 1
202
+ r = State.new(Matrix.column_vector(a), [x, i, y, z]).measure_partial(x, y, z).join.to_i(2)
203
+ case r
204
+ when 0
205
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
206
+ when 1
207
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
208
+ when 2
209
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
210
+ when 3
211
+ raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
212
+ when 4
213
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
214
+ when 5
215
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
216
+ when 6
217
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
218
+ when 7
219
+ raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
220
+ end
221
+ end
222
+
223
+ # # Quantum Teleportation
224
+ 1_000.times do
225
+ # alice will teleport 'a' to bob
226
+ a = Qubit.new(0, 1)
227
+ b = Qubit.new(1, 0)
228
+ c = Qubit.new(1, 0)
229
+ # dup so we have copy of the original state to verify transportation.
230
+ # In practice neither Alice nor Bob knows these values and our final check
231
+ # cannot be preformed. Through the maths we can understand that this holds
232
+ # regardless of not knowing initial values.
233
+ a_dup = a.dup
234
+
235
+ # first entangle 'b' and 'c'
236
+ s = C_NOT_GATE.*(H_GATE * b, c)
237
+ # bob takes 'c' far away
238
+
239
+ # alice continues
240
+ g = H_GATE.kronecker(I2).kronecker(I2) * C_NOT_GATE.kronecker(I2)
241
+ s = g.*(a, s)
242
+
243
+ # alice measure her two qubits and sends classical bits to bob
244
+ z, x = s.measure_partial(a, b)
245
+ case [z, x].join.to_i(2)
246
+ when 0
247
+ raise unless a == Qubit.new(1, 0) && b == Qubit.new(1, 0)
248
+ when 1
249
+ raise unless a == Qubit.new(1, 0) && b == Qubit.new(0, 1)
250
+ when 2
251
+ raise unless a == Qubit.new(0, 1) && b == Qubit.new(1, 0)
252
+ when 3
253
+ raise unless a == Qubit.new(0, 1) && b == Qubit.new(0, 1)
254
+ else
255
+ raise
256
+ end
257
+
258
+ # depending on what bobs gets, he applies gates to his qubit
259
+ # and is able to regain alice's 'a' qubit's original state instantly!
260
+ c = X_GATE * c if x == 1
261
+ c = Z_GATE * c if z == 1
262
+
263
+ # p [z, x]
264
+ # p a_dup, c
265
+ raise unless a_dup == c
266
+ end
@@ -0,0 +1,62 @@
1
+ require 'quantum_ruby'
2
+ # All the readme examples tested
3
+
4
+ # qubit_1 = Qubit.new(1, 0)
5
+ # qubit_1.measure # returns 0 bit
6
+ # qubit_2 = Qubit.new(0, 1)
7
+ # qubit_2.measure # returns 1 bit
8
+ qubit_1 = Qubit.new(1, 0)
9
+ raise unless qubit_1.measure.zero?
10
+ qubit_2 = Qubit.new(0, 1)
11
+ raise unless qubit_2.measure == 1
12
+
13
+ # # single qubit gate example (X_GATE ie. quantum NOT gate)
14
+ # qubit_1 = Qubit.new(0, 1) # equivalent to classical 1 bit
15
+ # state = X_GATE * qubit_1
16
+ # state.measure # returns 0 bit
17
+ # # multi qubit gate example (C_NOT_GATE ie. controlled NOT gate)
18
+ # control_qubit = Qubit.new(1, 0) # 0 bit
19
+ # target_qubit = Qubit.new(0, 1) # 1 bit
20
+ # # note multi params syntax: "GATE.*(param1, param2, etc.)"
21
+ # state = C_NOT_GATE.*(control_qubit, target_qubit)
22
+ # state.measure_partial(target_qubit) # return 1 bit since control is 0
23
+ # # again with control bit ON
24
+ # control_qubit = Qubit.new(0, 1) # 1 bit
25
+ # target_qubit = Qubit.new(0, 1) # 1 bit
26
+ # state = C_NOT_GATE.*(control_qubit, target_qubit)
27
+ # state.measure_partial(target_qubit) # return 0 bit since control is 1
28
+ # single qubit gate example (X_GATE ie. quantum NOT gate)
29
+ qubit_1 = Qubit.new(0, 1)
30
+ state = X_GATE * qubit_1
31
+ raise unless state.measure[0].zero?
32
+ control_qubit = Qubit.new(1, 0)
33
+ target_qubit = Qubit.new(0, 1)
34
+ state = C_NOT_GATE.*(control_qubit, target_qubit)
35
+ raise unless state.measure_partial(target_qubit)[0] == 1
36
+ # again with control bit ON
37
+ control_qubit = Qubit.new(0, 1) # 1 bit
38
+ target_qubit = Qubit.new(0, 1) # 1 bit
39
+ state = C_NOT_GATE.*(control_qubit, target_qubit)
40
+ raise unless state.measure_partial(target_qubit)[0].zero?
41
+
42
+ # x = Matrix[[0, 1], [1, 0]]
43
+ # y = Gate[[0, 1], [1, 0]]
44
+ x = Matrix[[0, 1], [1, 0]]
45
+ y = Gate[[0, 1], [1, 0]]
46
+
47
+ # Y_X_GATE = Y_GATE.kronecker(X_GATE)
48
+ Y_X_GATE = Y_GATE.kronecker(X_GATE)
49
+
50
+ # state = State.new(Matrix.column_vector([0, 0, 0, 1]))
51
+ # # H_GATE normally only works on 1 qubit ie. 2x1 matrix
52
+ # # the following will auto scale H_GATE with kronecker
53
+ # new_state = H_GATE.*(state, scale: :down)
54
+ state = State.new(Matrix.column_vector([0, 0, 0, 1]))
55
+ new_state = H_GATE.*(state, scale: :down)
56
+
57
+ # qubit_1 = Qubit.new(0, 1) # equivalent to classical 1 bit
58
+ # state = H_GATE * qubit_1
59
+ # state.measure # returns 0 bit or 1 bit with equal probability
60
+ qubit_1 = Qubit.new(0, 1)
61
+ state = H_GATE * qubit_1
62
+ raise unless state == State.new(Matrix.column_vector([1/Math.sqrt(2), -1/Math.sqrt(2)]))
@@ -0,0 +1,298 @@
1
+ require "quantum_ruby/version"
2
+ require 'matrix'
3
+
4
+ # Overrides for base Ruby classes
5
+ class Matrix
6
+ def kronecker(m)
7
+ raise ErrOperationNotDefined, [__method__, self.class, m.class] unless m.is_a?(Matrix)
8
+
9
+ a = Array.new(row_count * m.row_count) { Array.new(column_count * m.column_count) }
10
+
11
+ count_ver = 0
12
+ row_count.times do |i|
13
+ count_hor = 0
14
+ column_count.times do |j|
15
+ m.row_count.times do |p|
16
+ m.column_count.times do |q|
17
+ a[i + p + count_ver][j + q + count_hor] = self[i, j] * m[p, q]
18
+ end
19
+ end
20
+ count_hor += m.column_count - 1
21
+ end
22
+ count_ver += m.row_count - 1
23
+ end
24
+ self.class[*a]
25
+ end
26
+ end
27
+
28
+ class Complex
29
+ def round(digits)
30
+ Complex(real.round(digits), imag.round(digits))
31
+ end
32
+ end
33
+ # End of Overrides for base Ruby classes
34
+
35
+ ZERO_KET = '|0>'
36
+ ONE_KET = '|1>'
37
+ ADD_SYM = ' + '
38
+ ADD_SYM_SQUISH = '+'
39
+ I_SYM = 'i'
40
+
41
+ module ExceptionForQuantum
42
+ class NormalizationConstraint < StandardError
43
+ def initialize
44
+ super('Normalization constraint failed. Amplitudes must equal 1')
45
+ end
46
+ end
47
+
48
+ class ColumnMatrxiConstraint < StandardError
49
+ def initalize
50
+ super('Vector supplied must be a Matrix with a single column.')
51
+ end
52
+ end
53
+ end
54
+
55
+ module QuantumComplexPrinter
56
+ refine Complex do
57
+ def to_s
58
+ out = ''
59
+ out << real.round(3).to_s if real.positive?
60
+ out << ADD_SYM_SQUISH if real.positive? && imag
61
+ out << imag.round(3).to_s << I_SYM if imag
62
+ end
63
+ end
64
+ end
65
+
66
+ module QuantumVector
67
+ include ExceptionForQuantum
68
+ using QuantumComplexPrinter
69
+
70
+
71
+ PRECISION = 14
72
+
73
+ def state
74
+ out = '['
75
+ out << @vector[0, 0].to_s
76
+ @vector.drop(1).each do |i|
77
+ out << "\n " << i.to_s
78
+ end
79
+ out << ']'
80
+ end
81
+
82
+ def ==(other)
83
+ @vector.map { |i| i.round(PRECISION) } == other.vector.map { |i| i.round(PRECISION) }
84
+ end
85
+
86
+ private
87
+
88
+ def column_vector?
89
+ raise ColumnMatrxiConstraint unless @vector.is_a?(Matrix) && (@vector.column_count == 1)
90
+ end
91
+
92
+ def normalized?
93
+ raise NormalizationConstraint unless @vector.reduce(0) { |i, v| i + v.abs2 }.round(PRECISION) == 1
94
+ end
95
+ end
96
+
97
+ class Qubit
98
+ attr_reader :vector
99
+ attr_accessor :entangled
100
+ include QuantumVector
101
+ using QuantumComplexPrinter
102
+
103
+ ENTANGLED_WARNING = "Alert: This qubit is entangled.\n\tPlease locate and measure the State\n\tthat contains this qubit's superposition."
104
+
105
+ def initialize(zero, one)
106
+ @vector = Matrix[[zero], [one]]
107
+ column_vector?
108
+ normalized?
109
+ end
110
+
111
+ def to_s
112
+ return if entangled?
113
+
114
+ first = el(0)
115
+ last = el(1)
116
+
117
+ out = ''
118
+ out << first.to_s << ZERO_KET unless first.zero?
119
+ out << ADD_SYM unless first.zero? || last.zero?
120
+ out << last.to_s << ONE_KET unless last.zero?
121
+ out
122
+ end
123
+
124
+ def state
125
+ return(puts(ENTANGLED_WARNING)) if entangled?
126
+
127
+ super
128
+ end
129
+
130
+ def measure
131
+ if rand < @vector[0, 0].abs2
132
+ @vector = [1, 0]
133
+ 0
134
+ else
135
+ @vector = [0, 1]
136
+ 1
137
+ end
138
+ end
139
+
140
+ private
141
+
142
+ def el(row)
143
+ out = @vector[row, 0]
144
+ out.is_a?(Float) || out.is_a?(Complex) ? out.round(PRECISION) : out
145
+ end
146
+
147
+ def vector=(array)
148
+ @vector = Matrix.column_vector(array)
149
+ normalized?
150
+ end
151
+
152
+ def entangled?
153
+ if entangled
154
+ puts ENTANGLED_WARNING
155
+ true
156
+ else
157
+ false
158
+ end
159
+ end
160
+ end
161
+
162
+ class State
163
+ attr_reader :vector
164
+ attr_reader :qubits
165
+ include QuantumVector
166
+
167
+ def initialize(vector, *qubits)
168
+ @vector = vector
169
+ column_vector?
170
+ normalized?
171
+
172
+ @qubits = qubits.flatten.tap { |i| i.each { |j| j.entangled = true } }
173
+ end
174
+
175
+ def measure
176
+ # "determine' 'winner'
177
+ acc = 0
178
+ out = nil
179
+ secret = rand
180
+
181
+ @vector.to_a.each_with_index do |probability, index|
182
+ acc += probability[0].abs2
183
+ if acc > secret
184
+ out = index
185
+ break
186
+ end
187
+ end
188
+
189
+ # Reset state
190
+ @vector = Matrix.column_vector Array.new(@vector.row_count, 0)
191
+ # Update state
192
+ @vector.send(:[]=, out, 0, 1)
193
+
194
+ # Update each qubit
195
+ out = out.to_s(2).rjust(size, '0')
196
+ @qubits.each_with_index do |qubit, index|
197
+ qubit.entangled = false
198
+ qubit.send(:vector=, Array.new(2, 0).tap { |vector| vector[out[index].to_i] = 1 })
199
+ end
200
+
201
+ freeze
202
+ out.split('').map(&:to_i)
203
+ end
204
+
205
+ def measure_partial(*qubit)
206
+ # find location of our desired qubit(s)
207
+ qubit_ids = qubit.map { |i| @qubits.find_index { |j| j.hash == i .hash } }.sort
208
+
209
+ # collect probabilities for qubit(s) states
210
+ sub_result = @vector.to_a.flatten.each_with_index.group_by do |_probability, index|
211
+ qubit_ids.map do |id|
212
+ index.to_s(2).rjust(size, '0')[id]
213
+ end.join
214
+ end
215
+
216
+ # calculate final probabilities for qubit(s) state
217
+ probabilities = sub_result.sort.to_h.transform_values { |v| v.reduce(0) { |i, p| i + p[0].abs2 } }.values
218
+ acc = 0
219
+ out = nil
220
+ secret = rand
221
+
222
+ # "determine' 'winner'
223
+ probabilities.each_with_index do |probability, index|
224
+ acc += probability
225
+ if acc > secret
226
+ out = index
227
+ break
228
+ end
229
+ end
230
+
231
+ # Renormalize
232
+ squared_sum_mag = Math.sqrt(probabilities[out])
233
+ out = out.to_s(2).rjust(qubit.length, '0')
234
+ new_state = sub_result.fetch(out).map { |i| i[0] / squared_sum_mag }
235
+
236
+ # Update each qubit
237
+ @qubits.each_with_index do |q, i|
238
+ q.entangled = false
239
+ if index = qubit_ids.find_index(i)
240
+ q.send(:vector=, Array.new(2, 0).tap { |vector| vector[out[index].to_i] = 1 })
241
+ else
242
+ q.send(:vector=, new_state)
243
+ end
244
+ end
245
+ freeze
246
+ out.split('').map(&:to_i)
247
+ end
248
+
249
+ private
250
+
251
+ def size
252
+ Math.log(@vector.row_count, 2)
253
+ end
254
+ end
255
+
256
+ class Gate < Matrix
257
+ # can take gate, qubit, or state
258
+ def *(*args, scale: nil)
259
+ if scale
260
+ arg = begin
261
+ args[0].vector
262
+ rescue StandardError
263
+ args[0]
264
+ end
265
+ diff = (arg.row_count / row_count)
266
+ if diff > 1
267
+ return case scale
268
+ when :down
269
+ Gate[*kronecker(Matrix.identity(diff))].*(*args)
270
+ when :up
271
+ Gate[*Matrix.identity(diff).kronecker(self)].*(*args)
272
+ end
273
+ end
274
+ end
275
+
276
+ case args[0]
277
+ when State, Qubit
278
+ qubits = []
279
+ args = args.map do |i|
280
+ qubits << (i.is_a?(Qubit) ? i : i.qubits)
281
+ i.vector
282
+ end.reduce(:kronecker)
283
+ State.new(super(args), qubits)
284
+ else
285
+ super(*args)
286
+ end
287
+ end
288
+ end
289
+
290
+ I2 = Gate.identity(2)
291
+ X_GATE = Gate[[0, 1], [1, 0]]
292
+ Y_GATE = Gate[[0, -1i], [1i, 0]]
293
+ Z_GATE = Gate[[1, 0], [0, -1]]
294
+ H_GATE = 1 / Math.sqrt(2) * Gate[[1, 1], [1, -1]]
295
+ T_GATE = Gate[[1, 0], [0, Math::E**((1i * Math::PI) / 4)]]
296
+ C_NOT_GATE = Gate[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]
297
+ SWAP_GATE = Gate[[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]
298
+ TOFFOLI_GATE = Gate[[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0]]
@@ -0,0 +1,3 @@
1
+ module QuantumRuby
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/quantum_ruby/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "quantum_ruby"
5
+ spec.version = QuantumRuby::VERSION
6
+ spec.authors = ["Alessandro"]
7
+ spec.email = ["alessandro.minali@gmail.com"]
8
+
9
+ spec.summary = %q{A Quantum Computer Simulator written in Ruby.}
10
+ spec.description = %q{A Quantum Computer Simulator written in Ruby.}
11
+ spec.homepage = "https://github.com/AlessandroMinali/quantum_ruby"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quantum_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Alessandro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A Quantum Computer Simulator written in Ruby.
42
+ email:
43
+ - alessandro.minali@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/console
55
+ - bin/setup
56
+ - examples/general.rb
57
+ - examples/readme_examples.rb
58
+ - lib/quantum_ruby.rb
59
+ - lib/quantum_ruby/version.rb
60
+ - quantum_ruby.gemspec
61
+ homepage: https://github.com/AlessandroMinali/quantum_ruby
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.3.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.1.2
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A Quantum Computer Simulator written in Ruby.
84
+ test_files: []