1+ {
2+ "cells" : [
3+ {
4+ "cell_type" : " code" ,
5+ "execution_count" : null ,
6+ "metadata" : {},
7+ "outputs" : [],
8+ "source" : [
9+ " # Quantum Circuit with Single Output Qubit\n " ,
10+ " # 3 source qubits → entanglement → 1 output qubit (only this is measured)\n " ,
11+ " \n " ,
12+ " import sys\n " ,
13+ " sys.path.append('..')\n " ,
14+ " \n " ,
15+ " from modul.circuit import Circuit\n " ,
16+ " import numpy as np\n " ,
17+ " from qiskit import QuantumCircuit, transpile\n " ,
18+ " from qiskit_aer import AerSimulator\n " ,
19+ " from qiskit.quantum_info import Statevector\n " ,
20+ " from scipy.optimize import minimize\n " ,
21+ " import matplotlib.pyplot as plt\n " ,
22+ " \n " ,
23+ " # Input and training matrices\n " ,
24+ " input_matrix = np.eye(3)\n " ,
25+ " training_matrix = np.random.rand(3, 3)\n " ,
26+ " \n " ,
27+ " print(\" Input Matrix (Identity):\" )\n " ,
28+ " print(input_matrix)\n " ,
29+ " print(\"\\ nTraining Matrix:\" )\n " ,
30+ " print(training_matrix)\n " ,
31+ " \n " ,
32+ " def create_single_output_circuit(input_mat, training_mat):\n " ,
33+ " \"\"\" Create circuit: 3 source qubits → 1 output qubit\"\"\"\n " ,
34+ " qc = QuantumCircuit(4, 1) # 4 qubits, 1 measurement\n " ,
35+ " \n " ,
36+ " # Source qubits (0,1,2) with IP-TP-H-IP-TP-H-IP-TP\n " ,
37+ " for qubit in range(3):\n " ,
38+ " qc.p(float(input_mat[qubit, 0]), qubit)\n " ,
39+ " qc.p(float(training_mat[qubit, 0]), qubit)\n " ,
40+ " qc.h(qubit)\n " ,
41+ " qc.p(float(input_mat[qubit, 1]), qubit)\n " ,
42+ " qc.p(float(training_mat[qubit, 1]), qubit)\n " ,
43+ " qc.h(qubit)\n " ,
44+ " qc.p(float(input_mat[qubit, 2]), qubit)\n " ,
45+ " qc.p(float(training_mat[qubit, 2]), qubit)\n " ,
46+ " \n " ,
47+ " # Entangle all source qubits to output qubit (3)\n " ,
48+ " qc.cx(0, 3)\n " ,
49+ " qc.cx(1, 3) \n " ,
50+ " qc.cx(2, 3)\n " ,
51+ " \n " ,
52+ " # P-H-P-H-P on output qubit\n " ,
53+ " output_phases = [1.0, 2.0, 3.0] # Fixed for reproducibility\n " ,
54+ " qc.p(output_phases[0], 3)\n " ,
55+ " qc.h(3)\n " ,
56+ " qc.p(output_phases[1], 3)\n " ,
57+ " qc.h(3)\n " ,
58+ " qc.p(output_phases[2], 3)\n " ,
59+ " \n " ,
60+ " # Measure ONLY output qubit\n " ,
61+ " qc.measure(3, 0)\n " ,
62+ " \n " ,
63+ " return qc\n " ,
64+ " \n " ,
65+ " # Create and measure initial circuit\n " ,
66+ " simulator = AerSimulator()\n " ,
67+ " initial_circuit = create_single_output_circuit(input_matrix, training_matrix)\n " ,
68+ " \n " ,
69+ " print(\"\\ nCircuit (3 source → 1 output):\" )\n " ,
70+ " print(initial_circuit.draw(output='text'))\n " ,
71+ " \n " ,
72+ " # Initial measurement\n " ,
73+ " compiled = transpile(initial_circuit, simulator)\n " ,
74+ " job = simulator.run(compiled, shots=1000)\n " ,
75+ " counts = job.result().get_counts(compiled)\n " ,
76+ " \n " ,
77+ " print(\"\\ nInitial measurement (output qubit only):\" )\n " ,
78+ " for outcome, count in sorted(counts.items()):\n " ,
79+ " print(f\" Output |{outcome}>: {count} counts ({count/1000:.1%})\" )\n " ,
80+ " \n " ,
81+ " # Target: most probable output\n " ,
82+ " target_output = max(counts, key=counts.get)\n " ,
83+ " print(f\"\\ nTarget output: |{target_output}>\" )"
84+ ]
85+ },
86+ {
87+ "cell_type" : " code" ,
88+ "execution_count" : null ,
89+ "metadata" : {},
90+ "outputs" : [],
91+ "source" : [
92+ " # Two different input matrices with single output measurement\n " ,
93+ " print(\" TWO INPUT MATRICES → SINGLE OUTPUT MEASUREMENT\" )\n " ,
94+ " print(\" =\" * 60)\n " ,
95+ " \n " ,
96+ " # Generate different inputs\n " ,
97+ " input_matrix_1 = np.random.rand(3, 3)\n " ,
98+ " input_matrix_2 = np.random.rand(3, 3) \n " ,
99+ " shared_training = np.random.rand(3, 3)\n " ,
100+ " \n " ,
101+ " print(\" Input Matrix 1:\" )\n " ,
102+ " print(input_matrix_1)\n " ,
103+ " print(\"\\ nInput Matrix 2:\" ) \n " ,
104+ " print(input_matrix_2)\n " ,
105+ " \n " ,
106+ " # Test both inputs until we get different most probable outputs\n " ,
107+ " max_attempts = 50\n " ,
108+ " for attempt in range(max_attempts):\n " ,
109+ " # Create circuits for both inputs\n " ,
110+ " circuit_1 = create_single_output_circuit(input_matrix_1, shared_training)\n " ,
111+ " circuit_2 = create_single_output_circuit(input_matrix_2, shared_training)\n " ,
112+ " \n " ,
113+ " # Measure both\n " ,
114+ " compiled_1 = transpile(circuit_1, simulator)\n " ,
115+ " compiled_2 = transpile(circuit_2, simulator)\n " ,
116+ " \n " ,
117+ " job_1 = simulator.run(compiled_1, shots=1000)\n " ,
118+ " job_2 = simulator.run(compiled_2, shots=1000)\n " ,
119+ " \n " ,
120+ " counts_1 = job_1.result().get_counts(compiled_1)\n " ,
121+ " counts_2 = job_2.result().get_counts(compiled_2)\n " ,
122+ " \n " ,
123+ " target_1 = max(counts_1, key=counts_1.get)\n " ,
124+ " target_2 = max(counts_2, key=counts_2.get)\n " ,
125+ " \n " ,
126+ " if target_1 != target_2:\n " ,
127+ " print(f\"\\ nSuccess after {attempt + 1} attempts!\" )\n " ,
128+ " print(f\" Input 1 → Output |{target_1}> ({counts_1[target_1]/1000:.1%})\" )\n " ,
129+ " print(f\" Input 2 → Output |{target_2}> ({counts_2[target_2]/1000:.1%})\" )\n " ,
130+ " break\n " ,
131+ " \n " ,
132+ " # Regenerate if same output\n " ,
133+ " input_matrix_1 = np.random.rand(3, 3)\n " ,
134+ " input_matrix_2 = np.random.rand(3, 3)\n " ,
135+ " shared_training = np.random.rand(3, 3)\n " ,
136+ " \n " ,
137+ " # Store results\n " ,
138+ " stored_data = {\n " ,
139+ " 'input_1': input_matrix_1,\n " ,
140+ " 'input_2': input_matrix_2, \n " ,
141+ " 'training': shared_training,\n " ,
142+ " 'target_1': target_1,\n " ,
143+ " 'target_2': target_2,\n " ,
144+ " 'counts_1': counts_1,\n " ,
145+ " 'counts_2': counts_2\n " ,
146+ " }\n " ,
147+ " \n " ,
148+ " print(\"\\ nBoth inputs produce different output targets!\" )"
149+ ]
150+ },
151+ {
152+ "cell_type" : " code" ,
153+ "execution_count" : null ,
154+ "metadata" : {},
155+ "outputs" : [],
156+ "source" : [
157+ " # Train for Input 1's target output\n " ,
158+ " print(\" TRAINING FOR INPUT 1'S TARGET OUTPUT\" )\n " ,
159+ " print(\" =\" * 50)\n " ,
160+ " \n " ,
161+ " target_1 = stored_data['target_1']\n " ,
162+ " input_1 = stored_data['input_1']\n " ,
163+ " training_start = stored_data['training'].copy()\n " ,
164+ " \n " ,
165+ " print(f\" Target for Input 1: |{target_1}>\" )\n " ,
166+ " \n " ,
167+ " def objective_input_1(training_flat):\n " ,
168+ " training_mat = training_flat.reshape(3, 3)\n " ,
169+ " \n " ,
170+ " # Create circuit without measurement for analysis\n " ,
171+ " qc = QuantumCircuit(4)\n " ,
172+ " for qubit in range(3):\n " ,
173+ " qc.p(float(input_1[qubit, 0]), qubit)\n " ,
174+ " qc.p(float(training_mat[qubit, 0]), qubit)\n " ,
175+ " qc.h(qubit)\n " ,
176+ " qc.p(float(input_1[qubit, 1]), qubit) \n " ,
177+ " qc.p(float(training_mat[qubit, 1]), qubit)\n " ,
178+ " qc.h(qubit)\n " ,
179+ " qc.p(float(input_1[qubit, 2]), qubit)\n " ,
180+ " qc.p(float(training_mat[qubit, 2]), qubit)\n " ,
181+ " \n " ,
182+ " qc.cx(0, 3)\n " ,
183+ " qc.cx(1, 3)\n " ,
184+ " qc.cx(2, 3)\n " ,
185+ " \n " ,
186+ " output_phases = [1.0, 2.0, 3.0]\n " ,
187+ " qc.p(output_phases[0], 3)\n " ,
188+ " qc.h(3)\n " ,
189+ " qc.p(output_phases[1], 3)\n " ,
190+ " qc.h(3)\n " ,
191+ " qc.p(output_phases[2], 3)\n " ,
192+ " \n " ,
193+ " # Get marginal probability for output qubit\n " ,
194+ " state = Statevector.from_instruction(qc)\n " ,
195+ " probs = state.probabilities()\n " ,
196+ " \n " ,
197+ " if target_1 == '0':\n " ,
198+ " target_prob = sum(probs[i] for i in range(0, 16, 2)) # Even indices\n " ,
199+ " else:\n " ,
200+ " target_prob = sum(probs[i] for i in range(1, 16, 2)) # Odd indices\n " ,
201+ " \n " ,
202+ " return -target_prob\n " ,
203+ " \n " ,
204+ " # Optimize\n " ,
205+ " result_1 = minimize(\n " ,
206+ " objective_input_1,\n " ,
207+ " training_start.flatten() * 2 * np.pi,\n " ,
208+ " method='L-BFGS-B',\n " ,
209+ " bounds=[(0, 2*np.pi) for _ in range(9)]\n " ,
210+ " )\n " ,
211+ " \n " ,
212+ " trained_matrix_1 = result_1.x.reshape(3, 3) / (2 * np.pi)\n " ,
213+ " \n " ,
214+ " # Test result\n " ,
215+ " test_circuit_1 = create_single_output_circuit(input_1, trained_matrix_1)\n " ,
216+ " compiled_test = transpile(test_circuit_1, simulator)\n " ,
217+ " job_test = simulator.run(compiled_test, shots=1000)\n " ,
218+ " counts_test_1 = job_test.result().get_counts(compiled_test)\n " ,
219+ " \n " ,
220+ " print(f\"\\ nAfter training for Input 1:\" )\n " ,
221+ " for outcome, count in sorted(counts_test_1.items()):\n " ,
222+ " if outcome == target_1:\n " ,
223+ " print(f\" **|{outcome}>: {count} counts ({count/1000:.1%}) [TARGET]**\" )\n " ,
224+ " else:\n " ,
225+ " print(f\" |{outcome}>: {count} counts ({count/1000:.1%})\" )\n " ,
226+ " \n " ,
227+ " print(f\"\\ nImprovement: {counts_test_1[target_1]/1000:.1%} vs {stored_data['counts_1'][target_1]/1000:.1%}\" )"
228+ ]
229+ },
230+ {
231+ "cell_type" : " code" ,
232+ "execution_count" : null ,
233+ "metadata" : {},
234+ "outputs" : [],
235+ "source" : [
236+ " # Continue training for Input 2's target\n " ,
237+ " print(\" CONTINUING TRAINING FOR INPUT 2'S TARGET OUTPUT\" )\n " ,
238+ " print(\" =\" * 50)\n " ,
239+ " \n " ,
240+ " target_2 = stored_data['target_2']\n " ,
241+ " input_2 = stored_data['input_2']\n " ,
242+ " \n " ,
243+ " print(f\" Target for Input 2: |{target_2}>\" )\n " ,
244+ " print(f\" Starting from Input 1's trained matrix...\" )\n " ,
245+ " \n " ,
246+ " def objective_input_2(training_flat):\n " ,
247+ " training_mat = training_flat.reshape(3, 3)\n " ,
248+ " \n " ,
249+ " # Create circuit without measurement\n " ,
250+ " qc = QuantumCircuit(4)\n " ,
251+ " for qubit in range(3):\n " ,
252+ " qc.p(float(input_2[qubit, 0]), qubit)\n " ,
253+ " qc.p(float(training_mat[qubit, 0]), qubit)\n " ,
254+ " qc.h(qubit)\n " ,
255+ " qc.p(float(input_2[qubit, 1]), qubit)\n " ,
256+ " qc.p(float(training_mat[qubit, 1]), qubit)\n " ,
257+ " qc.h(qubit)\n " ,
258+ " qc.p(float(input_2[qubit, 2]), qubit)\n " ,
259+ " qc.p(float(training_mat[qubit, 2]), qubit)\n " ,
260+ " \n " ,
261+ " qc.cx(0, 3)\n " ,
262+ " qc.cx(1, 3)\n " ,
263+ " qc.cx(2, 3)\n " ,
264+ " \n " ,
265+ " output_phases = [1.0, 2.0, 3.0]\n " ,
266+ " qc.p(output_phases[0], 3)\n " ,
267+ " qc.h(3)\n " ,
268+ " qc.p(output_phases[1], 3)\n " ,
269+ " qc.h(3)\n " ,
270+ " qc.p(output_phases[2], 3)\n " ,
271+ " \n " ,
272+ " # Get marginal probability for output qubit\n " ,
273+ " state = Statevector.from_instruction(qc)\n " ,
274+ " probs = state.probabilities()\n " ,
275+ " \n " ,
276+ " if target_2 == '0':\n " ,
277+ " target_prob = sum(probs[i] for i in range(0, 16, 2))\n " ,
278+ " else:\n " ,
279+ " target_prob = sum(probs[i] for i in range(1, 16, 2))\n " ,
280+ " \n " ,
281+ " return -target_prob\n " ,
282+ " \n " ,
283+ " # Start from trained matrix 1\n " ,
284+ " result_2 = minimize(\n " ,
285+ " objective_input_2,\n " ,
286+ " trained_matrix_1.flatten() * 2 * np.pi,\n " ,
287+ " method='L-BFGS-B', \n " ,
288+ " bounds=[(0, 2*np.pi) for _ in range(9)]\n " ,
289+ " )\n " ,
290+ " \n " ,
291+ " final_trained_matrix = result_2.x.reshape(3, 3) / (2 * np.pi)\n " ,
292+ " \n " ,
293+ " print(\"\\ nFinal trained matrix:\" )\n " ,
294+ " print(final_trained_matrix)\n " ,
295+ " \n " ,
296+ " # Test both inputs with final matrix\n " ,
297+ " test_1_final = create_single_output_circuit(input_1, final_trained_matrix)\n " ,
298+ " test_2_final = create_single_output_circuit(input_2, final_trained_matrix)\n " ,
299+ " \n " ,
300+ " job_1_final = simulator.run(transpile(test_1_final, simulator), shots=1000)\n " ,
301+ " job_2_final = simulator.run(transpile(test_2_final, simulator), shots=1000)\n " ,
302+ " \n " ,
303+ " counts_1_final = job_1_final.result().get_counts()\n " ,
304+ " counts_2_final = job_2_final.result().get_counts()\n " ,
305+ " \n " ,
306+ " print(f\"\\ nFINAL RESULTS:\" )\n " ,
307+ " print(f\" Input 1 → |{target_1}>: {counts_1_final.get(target_1, 0)/1000:.1%}\" )\n " ,
308+ " print(f\" Input 2 → |{target_2}>: {counts_2_final.get(target_2, 0)/1000:.1%}\" )\n " ,
309+ " \n " ,
310+ " # Visualization\n " ,
311+ " fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))\n " ,
312+ " \n " ,
313+ " # Input 1 results\n " ,
314+ " outcomes_1 = list(counts_1_final.keys())\n " ,
315+ " probs_1 = [counts_1_final[k]/1000 for k in outcomes_1]\n " ,
316+ " bars1 = ax1.bar(outcomes_1, probs_1, color='lightblue')\n " ,
317+ " if target_1 in outcomes_1:\n " ,
318+ " bars1[outcomes_1.index(target_1)].set_color('darkblue')\n " ,
319+ " ax1.set_title(f'Input 1 → Target |{target_1}>')\n " ,
320+ " ax1.set_ylabel('Probability')\n " ,
321+ " for i, p in enumerate(probs_1):\n " ,
322+ " ax1.text(i, p + 0.02, f'{p:.1%}', ha='center')\n " ,
323+ " \n " ,
324+ " # Input 2 results \n " ,
325+ " outcomes_2 = list(counts_2_final.keys())\n " ,
326+ " probs_2 = [counts_2_final[k]/1000 for k in outcomes_2]\n " ,
327+ " bars2 = ax2.bar(outcomes_2, probs_2, color='lightgreen')\n " ,
328+ " if target_2 in outcomes_2:\n " ,
329+ " bars2[outcomes_2.index(target_2)].set_color('darkgreen')\n " ,
330+ " ax2.set_title(f'Input 2 → Target |{target_2}>')\n " ,
331+ " ax2.set_ylabel('Probability')\n " ,
332+ " for i, p in enumerate(probs_2):\n " ,
333+ " ax2.text(i, p + 0.02, f'{p:.1%}', ha='center')\n " ,
334+ " \n " ,
335+ " plt.suptitle('Single Output Qubit Results with Final Trained Matrix')\n " ,
336+ " plt.tight_layout()\n " ,
337+ " plt.show()\n " ,
338+ " \n " ,
339+ " print(\"\\ n3 source qubits compressed to 1 output qubit via entanglement!\" )"
340+ ]
341+ }
342+ ],
343+ "metadata" : {
344+ "kernelspec" : {
345+ "display_name" : " Python 3" ,
346+ "language" : " python" ,
347+ "name" : " python3"
348+ },
349+ "language_info" : {
350+ "codemirror_mode" : {
351+ "name" : " ipython" ,
352+ "version" : 3
353+ },
354+ "file_extension" : " .py" ,
355+ "mimetype" : " text/x-python" ,
356+ "name" : " python" ,
357+ "nbconvert_exporter" : " python" ,
358+ "pygments_lexer" : " ipython3" ,
359+ "version" : " 3.8.0"
360+ }
361+ },
362+ "nbformat" : 4 ,
363+ "nbformat_minor" : 4
364+ }
0 commit comments