quantum_ruby 0.1.1 → 0.9.0
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +0 -1
- data/examples/auto_scaling_gates_examples.rb +36 -0
- data/examples/backward_control_gate_examples.rb +29 -0
- data/examples/bell_state_examples.rb +22 -0
- data/examples/building_complex_gate_examples.rb +29 -0
- data/examples/ket_examples.rb +13 -0
- data/examples/kronecker_examples.rb +11 -0
- data/examples/measuring_examples.rb +72 -0
- data/examples/measuring_partial_examples.rb +90 -0
- data/examples/quantum_teleporation_examples.rb +52 -0
- data/examples/readme_examples.rb +1 -1
- data/examples/simple_gate_examples.rb +24 -0
- data/lib/quantum_ruby.rb +169 -87
- data/lib/quantum_ruby/version.rb +1 -1
- metadata +11 -2
- data/examples/general.rb +0 -266
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a4adb23b5e25ad751274853dad70416389645bab876ab7aacfa239ce329f8e6
|
4
|
+
data.tar.gz: 5dd0190e206fd8dd80c93af2ab6ffec1044566bd5ab1083e3c6801699fb5b39a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b8cb10f0ae00388c456624a8df9aa8943e70e0644b799dc6335fc2fc98329dbac576716d23f9427618ae3e950354fff4835a01edd84f53a850a6032b254de3b
|
7
|
+
data.tar.gz: dd3d0d8cb7497b26cb22cae572377f7e452bff84d15b52ab5c18b6bef5888da1ac3f336a227fe824ca7490229df20f75742504a0ae027be40fb63ae373896f82
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# Auto scale gates
|
4
|
+
# Sometimes we want to apply a gate to N-qubits that takes only takes less than N inputs
|
5
|
+
# To accomodate this we can scale the gatewith the identity matrix.
|
6
|
+
# This has the effect of taking in a larger input size but still only affecting less than N inputs
|
7
|
+
# More details here:
|
8
|
+
# https://en.wikipedia.org/wiki/Quantum_logic_gate#Application_on_entangled_states
|
9
|
+
|
10
|
+
# Circuit:
|
11
|
+
# Before scaling
|
12
|
+
# x --- | H Gate | -- | C_NOT | -- | H Gate | -
|
13
|
+
# y ----------------- | GATE | ---------------
|
14
|
+
#
|
15
|
+
# After scaling
|
16
|
+
# x --- | H Gate | -- | C_NOT | -- | H Gate | -
|
17
|
+
# y ----------------- | GATE | -- | I2 Gate | -
|
18
|
+
x = Qubit.new(1, 0)
|
19
|
+
y = Qubit.new(1, 0)
|
20
|
+
step_1 = H_GATE * x # single input
|
21
|
+
step_2 = C_NOT_GATE.*(step_1, y) # double input, so future gates must accept 2 qubits
|
22
|
+
raise unless H_GATE.*(step_2, scale: :down) == State.new(0.5 * Matrix[[1], [1], [1], [-1]])
|
23
|
+
|
24
|
+
# Circuit:
|
25
|
+
# Before scaling
|
26
|
+
# x --- | H Gate | -- | C_NOT | ---------------
|
27
|
+
# y ----------------- | GATE | -- | H Gate | -
|
28
|
+
#
|
29
|
+
# After scaling
|
30
|
+
# x --- | H Gate | -- | C_NOT | -- | I2 Gate | -
|
31
|
+
# y ----------------- | GATE | -- | H Gate | -
|
32
|
+
x = Qubit.new(1, 0)
|
33
|
+
y = Qubit.new(1, 0)
|
34
|
+
step_1 = H_GATE * x # single input
|
35
|
+
step_2 = C_NOT_GATE.*(step_1, y) # double input, so future gates must accept 2 qubits
|
36
|
+
raise unless H_GATE.*(step_2, scale: :up) == State.new(0.5 * Matrix[[1], [1], [1], [-1]])
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# In classical computing a control bit only affects the target bit
|
4
|
+
# These can also be done with a quantum circuit
|
5
|
+
# Circuit:
|
6
|
+
# x ----- | C_NOT | ----- x
|
7
|
+
# y ----- | GATE | ----- y flipped
|
8
|
+
x = Qubit.new(0, 1) # the control bit, 1
|
9
|
+
y = Qubit.new(1, 0) # the target bit, 0
|
10
|
+
C_NOT_GATE.*(x, y).measure
|
11
|
+
# Since the control bit is set, we expect the target to flip
|
12
|
+
raise unless y == Qubit.new(0, 1)
|
13
|
+
|
14
|
+
# What is strange is that depending on how the qubits are entangled we can have
|
15
|
+
# the target bit toggling the control bit!
|
16
|
+
# Details can be read here:
|
17
|
+
# https://en.wikipedia.org/wiki/Controlled_NOT_gate#Constructing_the_Bell_State_%7F'"`UNIQ--postMath-00000039-QINU`"'%7F
|
18
|
+
# https://quantum.country/qcvc#the-controlled-not-gate
|
19
|
+
|
20
|
+
# Backwards control gate
|
21
|
+
# Circuit:
|
22
|
+
# x ---| H | -- | C_NOT | -- | H |---- x flipped
|
23
|
+
# y ---| Gate | -- | GATE | -- | Gate |---- y
|
24
|
+
x = Qubit.new(1, 0) # the control bit, 1
|
25
|
+
y = Qubit.new(0, 1) # the target bit, 0
|
26
|
+
h_big = H_GATE.kronecker(H_GATE)
|
27
|
+
raise unless (h_big * (C_NOT_GATE * h_big)).*(x, y).measure
|
28
|
+
# control bit is flipped but target is the same!
|
29
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# Bell state
|
4
|
+
# Maximally entangle qubits. This is one of the coolest results of quantum computing.
|
5
|
+
# Allows for things such as reverse control gates and quantum teleportation.
|
6
|
+
# More details here:
|
7
|
+
# https://en.wikipedia.org/wiki/Bell_state#Applications
|
8
|
+
# https://en.wikipedia.org/wiki/Controlled_NOT_gate#Constructing_the_Bell_State_%7F'"`UNIQ--postMath-00000039-QINU`"'%7F
|
9
|
+
|
10
|
+
# Circuit:
|
11
|
+
# x ---| H | -- | C_NOT | -
|
12
|
+
# y ---| Gate | -- | GATE | -
|
13
|
+
|
14
|
+
# My lay man understanding of maximal entanglement comes from the state that is produced here.
|
15
|
+
# Either the result can be 00 or 11 based on the probabilities. In this sense the qubits
|
16
|
+
# are completely dependent on one another for the outcome ie. if one is 0 the other is 0, and likewise
|
17
|
+
# if one is 1 the other must also be 1. This property holds over great distances so even after separating
|
18
|
+
# if either is measure the partner instantly knows the result. This should sound weird because cause and
|
19
|
+
# effect would normally be though as being limited by the speed of light.
|
20
|
+
x = Qubit.new(1, 0)
|
21
|
+
y = Qubit.new(1, 0)
|
22
|
+
raise unless C_NOT_GATE.*(H_GATE * x, y) == State.new(Matrix[[0.7071067811865475], [0.0], [0.0], [0.7071067811865475]])
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# The Toffoli gate is a 3-qubit control gate (2 control, 1 target)
|
4
|
+
# We can build it from more basic quantum gates.
|
5
|
+
# We will use 3 gates to build this larger gate
|
6
|
+
# 1. Hadamard Gates (H_GATE)
|
7
|
+
# 2. Phase Shift Gates (T_GATE)
|
8
|
+
# 3. Control Gates (C_NOT_GATE)
|
9
|
+
|
10
|
+
# Helper function to perform adjoint of matrix
|
11
|
+
# More details here:
|
12
|
+
# https://en.wikipedia.org/wiki/Conjugate_transpose
|
13
|
+
class Matrix; def adjoint; conjugate.transpose; end; end
|
14
|
+
|
15
|
+
# Special variations on our base gates
|
16
|
+
# T_GATEa is just the adjoint of a T_GATE
|
17
|
+
T_3GATE_ADJOINT = I2.kronecker(I2).kronecker(T_GATE).adjoint
|
18
|
+
# C_NOT gate that takes 3 inputs, uses the top as control, ignores the middle, and uses the bottom as target
|
19
|
+
# More details here:
|
20
|
+
# https://quantumcomputing.stackexchange.com/questions/9614/how-to-interpret-a-4-qubit-quantum-circuit-as-a-matrix/9615#9615
|
21
|
+
# https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the-cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
|
22
|
+
C_3NOT_GATE = Gate[*Matrix[[1, 0], [0, 0]].kronecker(I2.kronecker(I2)) + Matrix[[0, 0], [0, 1]].kronecker(I2.kronecker(X_GATE))]
|
23
|
+
|
24
|
+
# Assembly
|
25
|
+
# Circuit:
|
26
|
+
# -- | Toffoli | -- ----------------------------|C_3NOT|---------------------------|C_3NOT|----------|C_NOT|--|T_GATE|--|C_NOT|-
|
27
|
+
# -- | Gate | -- == ----------|C_NOT|-----------| |----------|C_NOT|----------| |-|T_GATE|-|Gate |--|T_GATEa|-|Gate |-
|
28
|
+
# -- | | -- -|H_GATE|-|Gate |-|T_GATEa|-|Gate |-|T_GATE|-|Gate |-|T_GATE|-|Gate |-|T_GATE|-|H_GATE|-------------------
|
29
|
+
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
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
# 'Ket' visualization
|
3
|
+
# Supports Bra-ket notation
|
4
|
+
x = Qubit.new(1, 0)
|
5
|
+
raise unless x.to_s == '1|0>'
|
6
|
+
|
7
|
+
x = Qubit.new(0, 1)
|
8
|
+
raise unless x.to_s == '1|1>'
|
9
|
+
|
10
|
+
# Complex number visualization
|
11
|
+
x = Qubit.new((1 + 1i) / 2, 1i / Math.sqrt(2))
|
12
|
+
raise unless x.to_s == '1/2+1/2i|0> + 0.707i|1>'
|
13
|
+
raise unless x.state == "[1/2+1/2i\n 0.707i]"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
# Kronecker product test
|
3
|
+
# More details here:
|
4
|
+
# https://en.wikipedia.org/wiki/Kronecker_product
|
5
|
+
x = Matrix[[1, 2], [3, 4]]
|
6
|
+
y = Matrix[[0, 5], [6, 7]]
|
7
|
+
raise unless x.kronecker(y) == Matrix[[0, 5, 0, 10], [6, 7, 12, 14], [0, 15, 0, 20], [18, 21, 24, 28]]
|
8
|
+
|
9
|
+
x = Matrix[[1], [2]]
|
10
|
+
y = Matrix[[3], [4]]
|
11
|
+
raise unless x.kronecker(y) == Matrix[[3], [4], [6], [8]]
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# Measuring
|
4
|
+
1_000.times do
|
5
|
+
x = Qubit.new(1, 0).measure
|
6
|
+
raise unless x.zero?
|
7
|
+
|
8
|
+
x = Qubit.new(0, 1).measure
|
9
|
+
raise unless x == 1
|
10
|
+
|
11
|
+
# H_GATE puts qubits into a uniform superposition
|
12
|
+
s = (H_GATE * Qubit.new(0, 1))
|
13
|
+
case s.measure[0]
|
14
|
+
when 0
|
15
|
+
raise unless s == State.new(Matrix[[1], [0]])
|
16
|
+
when 1
|
17
|
+
raise unless s == State.new(Matrix[[0], [1]])
|
18
|
+
else
|
19
|
+
raise
|
20
|
+
end
|
21
|
+
|
22
|
+
# Multi qubit systems update individual qubits to their new state after a measurement occurs
|
23
|
+
x = Qubit.new(1, 0)
|
24
|
+
y = Qubit.new(0, 1)
|
25
|
+
a = Array.new(4, 0)
|
26
|
+
a[rand(4)] = 1
|
27
|
+
r = State.new(Matrix.column_vector(a), [x, y]).measure.join.to_i(2)
|
28
|
+
case r
|
29
|
+
when 0
|
30
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0)
|
31
|
+
when 1
|
32
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1)
|
33
|
+
when 2
|
34
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0)
|
35
|
+
when 3
|
36
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
x = Qubit.new(1, 0)
|
40
|
+
y = Qubit.new(0, 1)
|
41
|
+
z = Qubit.new(0, 1)
|
42
|
+
a = Array.new(8, 0)
|
43
|
+
a[rand(8)] = 1
|
44
|
+
r = State.new(Matrix.column_vector(a), [x, y, z]).measure.join.to_i(2)
|
45
|
+
case r
|
46
|
+
when 0
|
47
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
|
48
|
+
when 1
|
49
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
|
50
|
+
when 2
|
51
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
|
52
|
+
when 3
|
53
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
|
54
|
+
when 4
|
55
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
|
56
|
+
when 5
|
57
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
|
58
|
+
when 6
|
59
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
|
60
|
+
when 7
|
61
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# The following two show an example of reversing a previously applied Hadamard gate
|
66
|
+
# Circuit: 1/sqrt(2)|0> + 1/sqrt(2)|1> ----- H ------ 1|0>
|
67
|
+
x = Qubit.new(1 / Math.sqrt(2), 1 / Math.sqrt(2))
|
68
|
+
raise unless (H_GATE * x).measure[0].zero?
|
69
|
+
|
70
|
+
# Circuit: 1/sqrt(2)|0> + 1/sqrt(2)|1> ----- H ------ 1|1>
|
71
|
+
x = Qubit.new(1 / Math.sqrt(2), -1 / Math.sqrt(2))
|
72
|
+
raise unless (H_GATE * x).measure[0] == 1
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
# Partial Measure
|
3
|
+
# After a partial measure qubits are no longer equal
|
4
|
+
# One outcome is chosen for the desired qubit and all other qubits collapse to a
|
5
|
+
# normalized version of the new state
|
6
|
+
|
7
|
+
# y is measured in a system of 2 qubits
|
8
|
+
x = Qubit.new(0, 1)
|
9
|
+
y = Qubit.new(0, 1)
|
10
|
+
raise unless x == y
|
11
|
+
State.new(Matrix.column_vector([0, Math.sqrt(0.8), Math.sqrt(0.2), 0]), [x, y]).measure_partial(y)
|
12
|
+
raise if x == y
|
13
|
+
|
14
|
+
# y is measure in a system of 3 qubits
|
15
|
+
x = Qubit.new(0, 1)
|
16
|
+
y = Qubit.new(0, 1)
|
17
|
+
z = Qubit.new(0, 1)
|
18
|
+
State.new(Matrix.column_vector([0, 0.5, 0.5, 0, 0, 0.5, 0.5, 0]), [x, y, z]).measure_partial(y)
|
19
|
+
raise if (y == x) || (z == y)
|
20
|
+
|
21
|
+
# The following shows all possibles outcomes, randomized and verified
|
22
|
+
# Can be a good way to understand partial measuring by considering the logics tables
|
23
|
+
# More information can be found at these resources:
|
24
|
+
# https://cs.stackexchange.com/questions/71462/how-are-partial-measurements-performed-on-a-n-qubit-quantum-circuit
|
25
|
+
# https://quantum.country/teleportation#background_partial_measurement
|
26
|
+
1_000.times do
|
27
|
+
x = Qubit.new(0, 1)
|
28
|
+
y = Qubit.new(0, 1)
|
29
|
+
z = Qubit.new(0, 1)
|
30
|
+
a = Array.new(8, 0)
|
31
|
+
a[rand(8)] = 1
|
32
|
+
r = State.new(Matrix.column_vector(a), [x, y, z]).measure_partial(y, z).join.to_i(2)
|
33
|
+
case r
|
34
|
+
when 0
|
35
|
+
raise unless y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
|
36
|
+
when 1
|
37
|
+
raise unless y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
|
38
|
+
when 2
|
39
|
+
raise unless y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
|
40
|
+
when 3
|
41
|
+
raise unless y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
|
42
|
+
else
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
|
46
|
+
i = Qubit.new(0, 1)
|
47
|
+
y = Qubit.new(0, 1)
|
48
|
+
z = Qubit.new(0, 1)
|
49
|
+
a = Array.new(8, 0)
|
50
|
+
a[rand(8)] = 1
|
51
|
+
r = State.new(Matrix.column_vector(a), [y, z, i]).measure_partial(i, y).join.to_i(2)
|
52
|
+
case r
|
53
|
+
when 0
|
54
|
+
raise unless y == Qubit.new(1, 0) && i == Qubit.new(1, 0)
|
55
|
+
when 1
|
56
|
+
raise unless y == Qubit.new(1, 0) && i == Qubit.new(0, 1)
|
57
|
+
when 2
|
58
|
+
raise unless y == Qubit.new(0, 1) && i == Qubit.new(1, 0)
|
59
|
+
when 3
|
60
|
+
raise unless y == Qubit.new(0, 1) && i == Qubit.new(0, 1)
|
61
|
+
else
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
x = Qubit.new(0, 1)
|
66
|
+
y = Qubit.new(0, 1)
|
67
|
+
z = Qubit.new(0, 1)
|
68
|
+
i = Qubit.new(0, 1)
|
69
|
+
a = Array.new(16, 0)
|
70
|
+
a[rand(16)] = 1
|
71
|
+
r = State.new(Matrix.column_vector(a), [x, i, y, z]).measure_partial(x, y, z).join.to_i(2)
|
72
|
+
case r
|
73
|
+
when 0
|
74
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
|
75
|
+
when 1
|
76
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
|
77
|
+
when 2
|
78
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
|
79
|
+
when 3
|
80
|
+
raise unless x == Qubit.new(1, 0) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
|
81
|
+
when 4
|
82
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(1, 0)
|
83
|
+
when 5
|
84
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(1, 0) && z == Qubit.new(0, 1)
|
85
|
+
when 6
|
86
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(1, 0)
|
87
|
+
when 7
|
88
|
+
raise unless x == Qubit.new(0, 1) && y == Qubit.new(0, 1) && z == Qubit.new(0, 1)
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# Quantum Teleportation
|
4
|
+
# More details here:
|
5
|
+
# https://quantum.country/teleportation
|
6
|
+
# https://en.wikipedia.org/wiki/Quantum_teleportation#Non-technical_summary
|
7
|
+
# https://cs.uwaterloo.ca/~watrous/LectureNotes/CPSC519.Winter2006/04.pdf
|
8
|
+
|
9
|
+
1_000.times do
|
10
|
+
# alice will teleport 'a' to bob
|
11
|
+
a = Qubit.new(0, 1)
|
12
|
+
b = Qubit.new(1, 0)
|
13
|
+
c = Qubit.new(1, 0)
|
14
|
+
# dup so we have copy of the original state to verify transportation.
|
15
|
+
# In practice neither Alice nor Bob knows these values and our final check
|
16
|
+
# cannot be preformed. Through the maths we can understand that this holds
|
17
|
+
# regardless of not knowing initial values.
|
18
|
+
a_dup = a.dup
|
19
|
+
|
20
|
+
# first entangle 'b' and 'c'
|
21
|
+
s = C_NOT_GATE.*(H_GATE * b, c)
|
22
|
+
# bob takes 'c' far away
|
23
|
+
|
24
|
+
# Alice continues
|
25
|
+
g = H_GATE.kronecker(I2).kronecker(I2) * C_NOT_GATE.kronecker(I2)
|
26
|
+
s = g.*(a, s)
|
27
|
+
|
28
|
+
# Alice measure her two qubits and sends classical bits to bob
|
29
|
+
z, x = s.measure_partial(a, b)
|
30
|
+
case [z, x].join.to_i(2)
|
31
|
+
when 0
|
32
|
+
raise unless a == Qubit.new(1, 0) && b == Qubit.new(1, 0)
|
33
|
+
when 1
|
34
|
+
raise unless a == Qubit.new(1, 0) && b == Qubit.new(0, 1)
|
35
|
+
when 2
|
36
|
+
raise unless a == Qubit.new(0, 1) && b == Qubit.new(1, 0)
|
37
|
+
when 3
|
38
|
+
raise unless a == Qubit.new(0, 1) && b == Qubit.new(0, 1)
|
39
|
+
else
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
|
43
|
+
# depending on what bobs gets, he applies gates to his qubit
|
44
|
+
# and is able to regain Alice’s 'a' qubit's original state instantly!
|
45
|
+
c = X_GATE * c if x == 1
|
46
|
+
c = Z_GATE * c if z == 1
|
47
|
+
|
48
|
+
# Bob now knows Alice's original state. This is surprising, because Alice nor Bob
|
49
|
+
# needed to EVER know the state prior and this information is "instantly" transferred
|
50
|
+
# across any distance upon Alice's measurements
|
51
|
+
raise unless a_dup == c
|
52
|
+
end
|
data/examples/readme_examples.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../lib/quantum_ruby'
|
2
|
+
|
3
|
+
# NOT gate
|
4
|
+
# Circuit: 1|0> ----- X ------- 1|1>
|
5
|
+
z = Qubit.new(1, 0)
|
6
|
+
raise unless X_GATE * z == State.new(Matrix[[0], [1]])
|
7
|
+
|
8
|
+
# Hadamard Gate ie. Uniform superposition gate
|
9
|
+
# Circuit: 1|0> ----- H ------- 1/sqrt(2)|0> + 1/sqrt(2)|1>
|
10
|
+
z = Qubit.new(1, 0)
|
11
|
+
raise unless H_GATE * z == State.new(Matrix[[1 / Math.sqrt(2)], [1 / Math.sqrt(2)]])
|
12
|
+
|
13
|
+
# Demonstrate gate reversibility
|
14
|
+
# Since gates are unitary, applying them twice has the effect of reversing computation
|
15
|
+
# Circuit: 0.6|0> + 0.8|1> ---- H --- H --- 0.6|0> + 0.8|1>
|
16
|
+
z = Qubit.new(0.6, 0.8)
|
17
|
+
raise unless H_GATE * H_GATE * z == State.new(Matrix[[0.6], [0.8]])
|
18
|
+
|
19
|
+
# A mathematical demonstration of gate reversibility ie. Unitary inversion of gates
|
20
|
+
# More details here:
|
21
|
+
# https://en.wikipedia.org/wiki/Unitary_matrix
|
22
|
+
# https://quantum.country/qcvc#what-does-it-mean-to-be-unitary
|
23
|
+
class Matrix; def adjoint; conjugate.transpose; end; end # helper function
|
24
|
+
raise unless (C_NOT_GATE * H_GATE.kronecker(Matrix.identity(2))).adjoint == H_GATE.kronecker(Matrix.identity(2)) * C_NOT_GATE
|
data/lib/quantum_ruby.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'quantum_ruby/version'
|
2
2
|
require 'matrix'
|
3
3
|
|
4
|
-
#
|
4
|
+
#
|
5
|
+
# Overrides for base ruby base classes
|
5
6
|
class Matrix
|
7
|
+
#
|
8
|
+
# Takes two arbitrary sized resulting in a block matrix
|
9
|
+
# according to the Kronecker Product
|
10
|
+
# Details: https://en.wikipedia.org/wiki/Kronecker_product
|
6
11
|
def kronecker(m)
|
7
12
|
raise ErrOperationNotDefined, [__method__, self.class, m.class] unless m.is_a?(Matrix)
|
8
13
|
|
@@ -30,23 +35,23 @@ class Complex
|
|
30
35
|
Complex(real.round(digits), imag.round(digits))
|
31
36
|
end
|
32
37
|
end
|
33
|
-
# End of Overrides for base
|
38
|
+
# End of Overrides for base ruby base classes
|
34
39
|
|
35
|
-
ZERO_KET = '|0>'
|
36
|
-
ONE_KET = '|1>'
|
37
|
-
ADD_SYM = ' + '
|
38
|
-
ADD_SYM_SQUISH = '+'
|
39
|
-
I_SYM = 'i'
|
40
|
+
ZERO_KET = '|0>'.freeze
|
41
|
+
ONE_KET = '|1>'.freeze
|
42
|
+
ADD_SYM = ' + '.freeze
|
43
|
+
ADD_SYM_SQUISH = '+'.freeze
|
44
|
+
I_SYM = 'i'.freeze
|
40
45
|
|
41
46
|
module ExceptionForQuantum
|
42
47
|
class NormalizationConstraint < StandardError
|
43
48
|
def initialize
|
44
|
-
super('Normalization constraint failed.
|
49
|
+
super('Normalization constraint failed. Sum of squared amplitudes must equal 1')
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
48
53
|
class ColumnMatrxiConstraint < StandardError
|
49
|
-
def
|
54
|
+
def initialize
|
50
55
|
super('Vector supplied must be a Matrix with a single column.')
|
51
56
|
end
|
52
57
|
end
|
@@ -63,13 +68,18 @@ module QuantumComplexPrinter
|
|
63
68
|
end
|
64
69
|
end
|
65
70
|
|
71
|
+
#
|
72
|
+
# Contains helper functions for Qubit and State objects
|
66
73
|
module QuantumVector
|
67
74
|
include ExceptionForQuantum
|
68
75
|
using QuantumComplexPrinter
|
69
76
|
|
70
|
-
|
71
77
|
PRECISION = 14
|
72
78
|
|
79
|
+
def ==(other)
|
80
|
+
@vector.map { |i| i.round(PRECISION) } == other.vector.map { |i| i.round(PRECISION) }
|
81
|
+
end
|
82
|
+
|
73
83
|
def state
|
74
84
|
out = '['
|
75
85
|
out << @vector[0, 0].to_s
|
@@ -79,35 +89,74 @@ module QuantumVector
|
|
79
89
|
out << ']'
|
80
90
|
end
|
81
91
|
|
82
|
-
def ==(other)
|
83
|
-
@vector.map { |i| i.round(PRECISION) } == other.vector.map { |i| i.round(PRECISION) }
|
84
|
-
end
|
85
|
-
|
86
92
|
private
|
87
93
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
94
|
+
#
|
95
|
+
# Qubit and State vectors must be normalized so that the sum
|
96
|
+
# of squared amplitudes is equal to 1. This is a probability constraints saying that probabilities
|
97
|
+
# must sum to 1
|
98
|
+
# Qubit.new(1, 1) # fail
|
99
|
+
# Qubit.new(1, 0) # pass
|
100
|
+
# Qubit.new(1/Math.sqrt(2), 1/Math.sqrt(2)) # pass
|
92
101
|
def normalized?
|
93
102
|
raise NormalizationConstraint unless @vector.reduce(0) { |i, v| i + v.abs2 }.round(PRECISION) == 1
|
94
103
|
end
|
95
104
|
end
|
96
105
|
|
106
|
+
#
|
107
|
+
# The +Qubit+ class represents quantum bit. They are stored a 2-dimensional column vector.
|
108
|
+
# Qubits can be operated on via matrix multiplication or measurement.
|
97
109
|
class Qubit
|
98
|
-
attr_reader
|
110
|
+
attr_reader :vector
|
99
111
|
attr_accessor :entangled
|
100
112
|
include QuantumVector
|
101
113
|
using QuantumComplexPrinter
|
102
114
|
|
103
|
-
ENTANGLED_WARNING = "Alert: This qubit is entangled.\n\tPlease locate and measure the State\n\tthat contains this qubit's superposition."
|
115
|
+
ENTANGLED_WARNING = "Alert: This qubit is entangled.\n\tPlease locate and measure the State\n\tthat contains this qubit's superposition.".freeze
|
104
116
|
|
117
|
+
#
|
118
|
+
# Takes two values for the superposition of |0> and |1> respectively and store it in a Qubit
|
119
|
+
# Values must obey the normalization constraint
|
105
120
|
def initialize(zero, one)
|
106
121
|
@vector = Matrix[[zero], [one]]
|
107
|
-
column_vector?
|
108
122
|
normalized?
|
109
123
|
end
|
110
124
|
|
125
|
+
#
|
126
|
+
# Measurement can only happen at the expense of information elsewhere in the system.
|
127
|
+
# If two qubits are combined then their combined state becomes dependent so doing something to one
|
128
|
+
# can affect the other. This applies both to further operations or measurement.
|
129
|
+
# After measurement all related qubits' superpositions collapse to a classical interpretation, either 0 or 1
|
130
|
+
# Qubit.news(1, 0).to_s # 0, qubit remains the same
|
131
|
+
# Qubit.news(0, 1).to_s # 1, qubit remains the same
|
132
|
+
# Qubit.new(1/Math.sqrt(2), 1/Math.sqrt(2)) # either 0 or 1 and qubit superposition collapses
|
133
|
+
# to look like one the above qubits depending on the outcome
|
134
|
+
def measure
|
135
|
+
if rand < @vector[0, 0].abs2
|
136
|
+
@vector = [1, 0]
|
137
|
+
0
|
138
|
+
else
|
139
|
+
@vector = [0, 1]
|
140
|
+
1
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Return the qubit's superposition as a pretty printed array
|
146
|
+
# Qubit.news(1, 0).state
|
147
|
+
# # [1
|
148
|
+
# # 0]
|
149
|
+
def state
|
150
|
+
return if entangled?
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Returns the qubit's superposition in Bra-Ket notation
|
157
|
+
# Qubit.news(1, 0).to_s # 1|0>
|
158
|
+
# Qubit.news(0, 1).to_s # 1|1>
|
159
|
+
# Qubit.new(1/Math.sqrt(2), 1/Math.sqrt(2)) # 0.707|0> + 0.707|1>
|
111
160
|
def to_s
|
112
161
|
return if entangled?
|
113
162
|
|
@@ -121,22 +170,6 @@ class Qubit
|
|
121
170
|
out
|
122
171
|
end
|
123
172
|
|
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
173
|
private
|
141
174
|
|
142
175
|
def el(row)
|
@@ -144,26 +177,32 @@ class Qubit
|
|
144
177
|
out.is_a?(Float) || out.is_a?(Complex) ? out.round(PRECISION) : out
|
145
178
|
end
|
146
179
|
|
180
|
+
def entangled?
|
181
|
+
puts ENTANGLED_WARNING if entangled
|
182
|
+
entangled
|
183
|
+
end
|
184
|
+
|
147
185
|
def vector=(array)
|
148
186
|
@vector = Matrix.column_vector(array)
|
149
187
|
normalized?
|
150
188
|
end
|
151
|
-
|
152
|
-
def entangled?
|
153
|
-
if entangled
|
154
|
-
puts ENTANGLED_WARNING
|
155
|
-
true
|
156
|
-
else
|
157
|
-
false
|
158
|
-
end
|
159
|
-
end
|
160
189
|
end
|
161
190
|
|
191
|
+
#
|
192
|
+
# The +State+ class represents a combination of qubits' superpositions. They are stored a 2**n-dimensional column vector where
|
193
|
+
# N is the numbers of qubits entangled.
|
194
|
+
# State is usually produce after the applications of gates to a number of qubits and is continually passed forward through
|
195
|
+
# the quantum circuit until a measurement is made.
|
196
|
+
# State can be operated on via matrix multiplication or measurement(partial or otherwise).
|
162
197
|
class State
|
163
198
|
attr_reader :vector
|
164
199
|
attr_reader :qubits
|
165
200
|
include QuantumVector
|
166
201
|
|
202
|
+
#
|
203
|
+
# Takes a column matrix representing N qubits and the corresponding superposition
|
204
|
+
# Values of the matrix must obey the normalization constraint
|
205
|
+
# Additional takes references to the actual qubits so that they can be updated in the future
|
167
206
|
def initialize(vector, *qubits)
|
168
207
|
@vector = vector
|
169
208
|
column_vector?
|
@@ -172,36 +211,17 @@ class State
|
|
172
211
|
@qubits = qubits.flatten.tap { |i| i.each { |j| j.entangled = true } }
|
173
212
|
end
|
174
213
|
|
214
|
+
#
|
215
|
+
# Returns an array of bits representing the final state of all entangled qubits
|
216
|
+
# All qubits are written with their new state and all superposition information is lost
|
175
217
|
def measure
|
176
|
-
|
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)
|
218
|
+
measure_partial(@qubits)
|
203
219
|
end
|
204
220
|
|
221
|
+
#
|
222
|
+
# Takes an array of qubits for which a measurement should be made
|
223
|
+
# Returns an array of bits representing the final state of the requested qubits
|
224
|
+
# All others qubits are written with a normalized state and all superposition information is lost
|
205
225
|
def measure_partial(*qubit)
|
206
226
|
# find location of our desired qubit(s)
|
207
227
|
qubit_ids = qubit.map { |i| @qubits.find_index { |j| j.hash == i .hash } }.sort
|
@@ -215,11 +235,11 @@ class State
|
|
215
235
|
|
216
236
|
# calculate final probabilities for qubit(s) state
|
217
237
|
probabilities = sub_result.sort.to_h.transform_values { |v| v.reduce(0) { |i, p| i + p[0].abs2 } }.values
|
238
|
+
|
239
|
+
# determine 'winner'
|
218
240
|
acc = 0
|
219
241
|
out = nil
|
220
242
|
secret = rand
|
221
|
-
|
222
|
-
# "determine' 'winner'
|
223
243
|
probabilities.each_with_index do |probability, index|
|
224
244
|
acc += probability
|
225
245
|
if acc > secret
|
@@ -236,25 +256,53 @@ class State
|
|
236
256
|
# Update each qubit
|
237
257
|
@qubits.each_with_index do |q, i|
|
238
258
|
q.entangled = false
|
239
|
-
if index = qubit_ids.find_index(i)
|
259
|
+
if (index = qubit_ids.find_index(i))
|
240
260
|
q.send(:vector=, Array.new(2, 0).tap { |vector| vector[out[index].to_i] = 1 })
|
241
261
|
else
|
242
262
|
q.send(:vector=, new_state)
|
243
263
|
end
|
244
264
|
end
|
245
|
-
|
265
|
+
|
266
|
+
# State should no longer be used
|
246
267
|
out.split('').map(&:to_i)
|
247
268
|
end
|
248
269
|
|
249
270
|
private
|
250
271
|
|
272
|
+
def column_vector?
|
273
|
+
raise ColumnMatrxiConstraint unless @vector.is_a?(Matrix) && (@vector.column_count == 1)
|
274
|
+
end
|
275
|
+
|
251
276
|
def size
|
252
277
|
Math.log(@vector.row_count, 2)
|
253
278
|
end
|
254
279
|
end
|
255
280
|
|
281
|
+
#
|
282
|
+
# The +Gate+ class represents quantum logic gates as matrices.
|
283
|
+
# These matrices should be square and unitary. It is up to the user to perform these checks if they want a custom gate.
|
284
|
+
# There are a number of gates provided in this gem:
|
285
|
+
# X_GATE
|
286
|
+
# Y_GATE
|
287
|
+
# Z_GATE
|
288
|
+
# H_GATE
|
289
|
+
# T_GATE
|
290
|
+
# C_NOT_GATE
|
291
|
+
# SWAP_GATE
|
292
|
+
# TOFFOLI_GATE
|
256
293
|
class Gate < Matrix
|
257
|
-
#
|
294
|
+
#
|
295
|
+
# Applies the 'effect' of the gate on the arguments.
|
296
|
+
# The matrix operation on a gate can take multiple arguments but the dimensions must match according to the general
|
297
|
+
# rules of matrix multiplication.
|
298
|
+
#
|
299
|
+
# Gates can be scaled if you want to only affect less than N qubits of a system. Scaling occurs either up or down. If both is desired
|
300
|
+
# choose one direction and manually scale the other direction.
|
301
|
+
# NOTE scaling is done brute force and may no be applicable to the needs of your circuit.
|
302
|
+
# Refer to scaling examples for more details: https://github.com/AlessandroMinali/exanples/auto_scaling_gates_examples.rb
|
303
|
+
#
|
304
|
+
# Can take Gate, Qubit, or State
|
305
|
+
# Results in a new Gate if supplied a Gate, otherwise returns a new State
|
258
306
|
def *(*args, scale: nil)
|
259
307
|
if scale
|
260
308
|
arg = begin
|
@@ -288,11 +336,45 @@ class Gate < Matrix
|
|
288
336
|
end
|
289
337
|
|
290
338
|
I2 = Gate.identity(2)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
339
|
+
|
340
|
+
X_GATE = Gate[
|
341
|
+
[0, 1],
|
342
|
+
[1, 0]]
|
343
|
+
|
344
|
+
Y_GATE = Gate[
|
345
|
+
[0, -1i],
|
346
|
+
[1i, 0]]
|
347
|
+
|
348
|
+
Z_GATE = Gate[
|
349
|
+
[1, 0],
|
350
|
+
[0, -1]]
|
351
|
+
|
352
|
+
H_GATE = 1 / Math.sqrt(2) * Gate[
|
353
|
+
[1, 1],
|
354
|
+
[1, -1]]
|
355
|
+
|
356
|
+
T_GATE = Gate[
|
357
|
+
[1, 0],
|
358
|
+
[0, Math::E**((1i * Math::PI) / 4)]]
|
359
|
+
|
360
|
+
C_NOT_GATE = Gate[
|
361
|
+
[1, 0, 0, 0],
|
362
|
+
[0, 1, 0, 0],
|
363
|
+
[0, 0, 0, 1],
|
364
|
+
[0, 0, 1, 0]]
|
365
|
+
|
366
|
+
SWAP_GATE = Gate[
|
367
|
+
[1, 0, 0, 0],
|
368
|
+
[0, 0, 1, 0],
|
369
|
+
[0, 1, 0, 0],
|
370
|
+
[0, 0, 0, 1]]
|
371
|
+
|
372
|
+
TOFFOLI_GATE = Gate[
|
373
|
+
[1, 0, 0, 0, 0, 0, 0, 0],
|
374
|
+
[0, 1, 0, 0, 0, 0, 0, 0],
|
375
|
+
[0, 0, 1, 0, 0, 0, 0, 0],
|
376
|
+
[0, 0, 0, 1, 0, 0, 0, 0],
|
377
|
+
[0, 0, 0, 0, 1, 0, 0, 0],
|
378
|
+
[0, 0, 0, 0, 0, 1, 0, 0],
|
379
|
+
[0, 0, 0, 0, 0, 0, 0, 1],
|
380
|
+
[0, 0, 0, 0, 0, 0, 1, 0]]
|
data/lib/quantum_ruby/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quantum_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro
|
@@ -53,8 +53,17 @@ files:
|
|
53
53
|
- Rakefile
|
54
54
|
- bin/console
|
55
55
|
- bin/setup
|
56
|
-
- examples/
|
56
|
+
- examples/auto_scaling_gates_examples.rb
|
57
|
+
- examples/backward_control_gate_examples.rb
|
58
|
+
- examples/bell_state_examples.rb
|
59
|
+
- examples/building_complex_gate_examples.rb
|
60
|
+
- examples/ket_examples.rb
|
61
|
+
- examples/kronecker_examples.rb
|
62
|
+
- examples/measuring_examples.rb
|
63
|
+
- examples/measuring_partial_examples.rb
|
64
|
+
- examples/quantum_teleporation_examples.rb
|
57
65
|
- examples/readme_examples.rb
|
66
|
+
- examples/simple_gate_examples.rb
|
58
67
|
- lib/quantum_ruby.rb
|
59
68
|
- lib/quantum_ruby/version.rb
|
60
69
|
- quantum_ruby.gemspec
|
data/examples/general.rb
DELETED
@@ -1,266 +0,0 @@
|
|
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
|