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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68be46a736a6bdd4f752c9232f65cddf546ca4dbf8b9b72f41fb6237860871e2
4
- data.tar.gz: b0a51c8d0e885817db849e60f87a187e20a90dce5113d0e2c4a9b8962c6a2f85
3
+ metadata.gz: 6a4adb23b5e25ad751274853dad70416389645bab876ab7aacfa239ce329f8e6
4
+ data.tar.gz: 5dd0190e206fd8dd80c93af2ab6ffec1044566bd5ab1083e3c6801699fb5b39a
5
5
  SHA512:
6
- metadata.gz: edbe804cdc48d763966e8b0f343aa8ab4d9dd00595f84b5951289311ca5f81623465b5ade7c5462b263b6402a4d81f4c86370a6360fd1e75db6b906462be1415
7
- data.tar.gz: 72c0d4713f3025e9702c45aa05a9c56746e59f715322c769db7df1fe4a95c8b60bff536149e460500ad470e43a4e0fc0be27230dfabfe903a40996a1bf9f0658
6
+ metadata.gz: 5b8cb10f0ae00388c456624a8df9aa8943e70e0644b799dc6335fc2fc98329dbac576716d23f9427618ae3e950354fff4835a01edd84f53a850a6032b254de3b
7
+ data.tar.gz: dd3d0d8cb7497b26cb22cae572377f7e452bff84d15b52ab5c18b6bef5888da1ac3f336a227fe824ca7490229df20f75742504a0ae027be40fb63ae373896f82
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantum_ruby (0.1.1)
4
+ quantum_ruby (0.9.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # QuantumRuby
3
2
 
4
3
  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.
@@ -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
@@ -1,4 +1,4 @@
1
- require 'quantum_ruby'
1
+ require_relative '../lib/quantum_ruby'
2
2
  # All the readme examples tested
3
3
 
4
4
  # qubit_1 = Qubit.new(1, 0)
@@ -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
@@ -1,8 +1,13 @@
1
- require "quantum_ruby/version"
1
+ require 'quantum_ruby/version'
2
2
  require 'matrix'
3
3
 
4
- # Overrides for base Ruby classes
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 Ruby classes
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. Amplitudes must equal 1')
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 initalize
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
- def column_vector?
89
- raise ColumnMatrxiConstraint unless @vector.is_a?(Matrix) && (@vector.column_count == 1)
90
- end
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 :vector
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
- # "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)
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
- freeze
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
- # can take gate, qubit, or state
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
- 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]]
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]]
@@ -1,3 +1,3 @@
1
1
  module QuantumRuby
2
- VERSION = "0.1.1"
2
+ VERSION = "0.9.0"
3
3
  end
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.1.1
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/general.rb
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
@@ -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