ashutosh1919 commited on
Commit
91d3e9c
1 Parent(s): 46568f0

Adding Perceptron and its simulation demo notebook

Browse files
PerceptronSimulator.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
images/circuit_2qubit.png ADDED
images/circuit_4qubit.png ADDED
quantum_perceptron/__init__.py CHANGED
@@ -0,0 +1 @@
 
 
1
+ from quantum_perceptron.perceptron import Perceptron
quantum_perceptron/perceptron.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ from qiskit import QuantumCircuit, Aer, execute
3
+ from quantum_perceptron.utils import (
4
+ assert_negative,
5
+ assert_bits,
6
+ create_hypergraph_state,
7
+ get_vector_from_int
8
+ )
9
+
10
+
11
+ class Perceptron:
12
+ def __init__(self,
13
+ num_qubits: int,
14
+ weight: int = 1,
15
+ input: int = 1):
16
+ """
17
+ This class creates a quantum perceptron instance which has
18
+ capability calculate input * weight. Note that we are not applying
19
+ any non-linearity. Our perceptron design is as per
20
+ https://arxiv.org/pdf/1811.02266.pdf
21
+
22
+ Args:
23
+ num_qubits: `int` denoting number of qubits in perceptron
24
+ weight: `int` denoting the weight of the perceptron.
25
+ input: `int` denoting the data to input to the perceptron.
26
+ """
27
+ self.num_qubits = num_qubits
28
+ assert self.num_qubits > 0, "Number qubits must be positive"
29
+ assert_negative(weight)
30
+ self.weight = weight
31
+ assert_negative(input)
32
+ self.input = input
33
+ assert_bits(self.weight, self.num_qubits)
34
+ assert_bits(self.input, self.num_qubits)
35
+ self.build_flag = False
36
+ self.build_circuit()
37
+
38
+ def Ui(self):
39
+ """
40
+ Sub-circuit to transform input data.
41
+ """
42
+ if not self.build_flag:
43
+ raise RuntimeError("Ui() cannot be called independently.")
44
+
45
+ Ui = QuantumCircuit(self.num_qubits)
46
+
47
+ # Applying hadamard to first num_qubits
48
+ for q in range(self.num_qubits):
49
+ Ui.h(q)
50
+
51
+ # Extracting vectors for input
52
+ input_vector = get_vector_from_int(self.input, self.num_qubits)
53
+
54
+ # Applying hypergraph state corresponding to input.
55
+ Ui = create_hypergraph_state(Ui,
56
+ input_vector,
57
+ self.num_qubits)
58
+ Ui = Ui.to_gate()
59
+ Ui.name = "U_i"
60
+ return Ui
61
+
62
+ def Uw(self):
63
+ """
64
+ Sub-circuit to transform weight data.
65
+ """
66
+ if not self.build_flag:
67
+ raise RuntimeError("Ui() cannot be called independently.")
68
+
69
+ Uw = QuantumCircuit(self.num_qubits)
70
+
71
+ # Extracting vectors for weight
72
+ input_vector = get_vector_from_int(self.weight, self.num_qubits)
73
+
74
+ # Applying hypergraph state corresponding to weight.
75
+ Uw = create_hypergraph_state(Uw,
76
+ input_vector,
77
+ self.num_qubits)
78
+
79
+ # Applying hadamard to first num_qubits
80
+ for q in range(self.num_qubits):
81
+ Uw.h(q)
82
+
83
+ # Applying X gate to first num_qubits
84
+ for q in range(self.num_qubits):
85
+ Uw.x(q)
86
+ Uw = Uw.to_gate()
87
+ Uw.name = "U_w"
88
+ return Uw
89
+
90
+ def build_circuit(self):
91
+ """
92
+ Build quantum circuit corresponding to single perceptron combining
93
+ input data and weight of the perceptron.
94
+ """
95
+ # Creating circuit with num_qubits + 1 (ancilla) qubit.
96
+ self.circuit = QuantumCircuit(1 + self.num_qubits, 1)
97
+
98
+ def toggle_build_flag():
99
+ """
100
+ Toggle the build circuit flag. Used to monitor Ui and Uf circuits
101
+ to ensure that those functions are not called seperately but from
102
+ the `build_circuit()` function.
103
+ """
104
+ self.build_flag = not self.build_flag
105
+
106
+ # Append Ui for processing input
107
+ toggle_build_flag()
108
+ # self.Ui()
109
+ self.circuit.append(
110
+ self.Ui(),
111
+ list(range(self.num_qubits))
112
+ )
113
+ toggle_build_flag()
114
+
115
+ # Append Uf for processing input
116
+ toggle_build_flag()
117
+ self.circuit.append(
118
+ self.Uw(),
119
+ list(range(self.num_qubits))
120
+ )
121
+ toggle_build_flag()
122
+
123
+ # Toffoli gate at the end with target as ancilla qubit
124
+ self.circuit.mcx(
125
+ control_qubits=list(range(self.num_qubits)),
126
+ target_qubit=self.num_qubits
127
+ )
128
+
129
+ # Measure the last qubit.
130
+ self.circuit.measure(self.num_qubits, 0)
131
+
132
+ def measure_circuit(self, num_iters: int = 1000) -> Dict[str, int]:
133
+ """
134
+ Measure the perceptron and get the counts of the final results.
135
+
136
+ Args:
137
+ num_iters: `int` denoting number of iterations to execute circuit.
138
+
139
+ Returns: `dict` containing the measurement frequencies.
140
+ """
141
+ if not hasattr(self, 'circuit'):
142
+ raise RuntimeError("The circuit hasn't yet built.",
143
+ "Please call build_circuit() first.")
144
+ backend = Aer.get_backend('qasm_simulator')
145
+
146
+ # Execute the circuit
147
+ job = execute(self.circuit, backend, shots=num_iters)
148
+
149
+ # Get result and counts
150
+ result = job.result()
151
+ counts = result.get_counts(self.circuit)
152
+ return dict(counts)
153
+
154
+ def save_circuit_image(self,
155
+ file_path: str,
156
+ output_format: str = "mpl"):
157
+ """
158
+ Save circuit to the image file.
159
+ """
160
+ if not hasattr(self, 'circuit'):
161
+ raise RuntimeError("The circuit hasn't yet built.",
162
+ "Please call build_circuit() first.")
163
+ self.circuit.draw(output=output_format, filename=file_path)
quantum_perceptron/tests/test_utils.py CHANGED
@@ -28,10 +28,11 @@ def test_get_bin_int(data, num_qubits, expected_result):
28
 
29
 
30
  @pytest.mark.parametrize("data, num_qubits, expected_result", [
31
- (12, 4, np.array([-1, -1, 1, 1])),
32
- (12, 5, np.array([1, -1, -1, 1, 1])),
33
- (1, 1, np.array([-1])),
34
- (12, 3, False),
 
35
  (-5, 2, False)
36
  ])
37
  def test_get_vector_from_int(data, num_qubits, expected_result):
 
28
 
29
 
30
  @pytest.mark.parametrize("data, num_qubits, expected_result", [
31
+ (12, 4, np.array([1]*12 + [-1, -1, 1, 1])),
32
+ (12, 5, np.array([1]*28 + [-1, -1, 1, 1])),
33
+ (1, 1, np.array([1, -1])),
34
+ (12, 3, np.array([1]*4 + [-1, -1, 1, 1])),
35
+ (16, 2, False),
36
  (-5, 2, False)
37
  ])
38
  def test_get_vector_from_int(data, num_qubits, expected_result):
quantum_perceptron/utils/__init__.py CHANGED
@@ -1 +1,2 @@
1
  from quantum_perceptron.utils.data_utils import *
 
 
1
  from quantum_perceptron.utils.data_utils import *
2
+ from quantum_perceptron.utils.quantum_utils import *
quantum_perceptron/utils/data_utils.py CHANGED
@@ -16,7 +16,7 @@ def get_bin_int(data: int, num_qubits: Optional[int] = None) -> str:
16
  """
17
  assert_negative(data)
18
  if num_qubits:
19
- return bin(data)[2:].zfill(num_qubits)
20
  return bin(data)[2:]
21
 
22
 
@@ -24,7 +24,7 @@ def assert_bits(data: int, num_bits: int):
24
  """
25
  General method to prevent invalid number of bits.
26
  """
27
- if len(get_bin_int(data)) > num_bits:
28
  raise ValueError("data has more bits than num_bits")
29
 
30
 
@@ -44,7 +44,7 @@ def get_vector_from_int(data: int, num_qubits: int) -> np.ndarray:
44
  assert_bits(data, num_qubits)
45
 
46
  bin_data = get_bin_int(data, num_qubits)
47
- data_vector = np.empty(num_qubits)
48
 
49
  for i, bit in enumerate(bin_data):
50
  data_vector[i] = np.power(-1, int(bit))
 
16
  """
17
  assert_negative(data)
18
  if num_qubits:
19
+ return bin(data)[2:].zfill(np.power(2, num_qubits))
20
  return bin(data)[2:]
21
 
22
 
 
24
  """
25
  General method to prevent invalid number of bits.
26
  """
27
+ if len(get_bin_int(data)) > np.power(2, num_bits):
28
  raise ValueError("data has more bits than num_bits")
29
 
30
 
 
44
  assert_bits(data, num_qubits)
45
 
46
  bin_data = get_bin_int(data, num_qubits)
47
+ data_vector = np.empty(np.power(2, num_qubits))
48
 
49
  for i, bit in enumerate(bin_data):
50
  data_vector[i] = np.power(-1, int(bit))
quantum_perceptron/utils/quantum_utils.py CHANGED
@@ -11,7 +11,8 @@ def append_hypergraph_state(
11
  circuit: QuantumCircuit,
12
  data_vector: np.ndarray,
13
  states: np.ndarray,
14
- ones_count: Dict[int, List[int]]) -> QuantumCircuit:
 
15
  """
16
  Append the computed hypergraph state to the circuit.
17
 
@@ -21,11 +22,11 @@ def append_hypergraph_state(
21
  states: `list` of `str` containing the bit strings for states.
22
  ones_count: `dict` containing mapping of the count of ones with
23
  index of states
 
24
 
25
  Returns: `QuantumCircuit` object denoting the circuit containing
26
  hypergraph states.
27
  """
28
- num_qubits = int(np.log2(len(data_vector)))
29
  is_sign_inverted = [1] * len(data_vector)
30
 
31
  # Flipping all signs if all zero state has coef -1.
@@ -56,7 +57,8 @@ def append_hypergraph_state(
56
 
57
 
58
  def create_hypergraph_state(circuit: QuantumCircuit,
59
- data_vector: np.ndarray) -> QuantumCircuit:
 
60
  """
61
  Creating hypergraph state for specific data vector corresponding to
62
  the provided data (input or weight value).
@@ -65,16 +67,17 @@ def create_hypergraph_state(circuit: QuantumCircuit,
65
  Args:
66
  circuit: `QuantumCircuit` object corresponding to the perceptron.
67
  data_vector: `np.ndarray` containing the data vector containing -1s & 1s.
 
68
 
69
  Returns: `QuantumCircuit` object denoting the circuit containing
70
  hypergraph states.
71
  """
72
- num_qubits = int(np.log2(len(data_vector)))
73
  states = get_possible_state_strings(num_qubits)
74
  ones_count = get_ones_counts_to_states(states)
75
  return append_hypergraph_state(
76
  circuit,
77
  data_vector,
78
  states,
79
- ones_count
 
80
  )
 
11
  circuit: QuantumCircuit,
12
  data_vector: np.ndarray,
13
  states: np.ndarray,
14
+ ones_count: Dict[int, List[int]],
15
+ num_qubits: int) -> QuantumCircuit:
16
  """
17
  Append the computed hypergraph state to the circuit.
18
 
 
22
  states: `list` of `str` containing the bit strings for states.
23
  ones_count: `dict` containing mapping of the count of ones with
24
  index of states
25
+ num_qubits: `int` denoting total number of qubits in the circuit.
26
 
27
  Returns: `QuantumCircuit` object denoting the circuit containing
28
  hypergraph states.
29
  """
 
30
  is_sign_inverted = [1] * len(data_vector)
31
 
32
  # Flipping all signs if all zero state has coef -1.
 
57
 
58
 
59
  def create_hypergraph_state(circuit: QuantumCircuit,
60
+ data_vector: np.ndarray,
61
+ num_qubits: int) -> QuantumCircuit:
62
  """
63
  Creating hypergraph state for specific data vector corresponding to
64
  the provided data (input or weight value).
 
67
  Args:
68
  circuit: `QuantumCircuit` object corresponding to the perceptron.
69
  data_vector: `np.ndarray` containing the data vector containing -1s & 1s.
70
+ num_qubits: `int` denoting total number of qubits in the circuit.
71
 
72
  Returns: `QuantumCircuit` object denoting the circuit containing
73
  hypergraph states.
74
  """
 
75
  states = get_possible_state_strings(num_qubits)
76
  ones_count = get_ones_counts_to_states(states)
77
  return append_hypergraph_state(
78
  circuit,
79
  data_vector,
80
  states,
81
+ ones_count,
82
+ num_qubits
83
  )