-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.xml
2162 lines (1690 loc) · 129 KB
/
index.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Valter</title>
<link>https://valteruo.github.io/</link>
<atom:link href="https://valteruo.github.io/index.xml" rel="self" type="application/rss+xml" />
<description>Valter</description>
<generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><lastBuildDate>Fri, 01 Sep 2023 08:00:00 +0000</lastBuildDate>
<image>
<url>https://valteruo.github.io/media/icon_hu0b7a4cb9992c9ac0e91bd28ffd38dd00_9727_512x512_fill_lanczos_center_3.png</url>
<title>Valter</title>
<link>https://valteruo.github.io/</link>
</image>
<item>
<title>The International Workshop on Quantum Data Science and Management (QDSM 2023)</title>
<link>https://valteruo.github.io/talk/the-international-workshop-on-quantum-data-science-and-management-qdsm-2023/</link>
<pubDate>Fri, 01 Sep 2023 08:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/talk/the-international-workshop-on-quantum-data-science-and-management-qdsm-2023/</guid>
<description></description>
</item>
<item>
<title>Quantum Machine Learning: Foundation, New Techniques, and Opportunities for Database Research</title>
<link>https://valteruo.github.io/publication/sigmod23/</link>
<pubDate>Thu, 22 Jun 2023 01:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/publication/sigmod23/</guid>
<description><p>See <a href="https://www.helsinki.fi/en/researchgroups/unified-database-management-systems-udbms/sigmod-2023-tutorial" target="_blank" rel="noopener">tutorial&rsquo;s website</a> for more details. I will update the paper and more information when they are available.</p>
</description>
</item>
<item>
<title>SQL Query Classification with A Quantum Natural Language Processing Approach</title>
<link>https://valteruo.github.io/talk/sql-query-classification-with-a-quantum-natural-language-processing-approach/</link>
<pubDate>Wed, 05 Apr 2023 17:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/talk/sql-query-classification-with-a-quantum-natural-language-processing-approach/</guid>
<description><p>Abstract from <a href="https://www.uio.no/dscience/english/news-and-events/events/dscience-brain-talk-5.html" target="_blank" rel="noopener">BrainTalk&rsquo;s website</a>:</p>
<p>This work proposes a quantum natural language processing-inspired approach for classifying SQL queries based on their execution times and cardinalities. Using parameterized quantum circuits and an iterative method for their optimization, we estimate query metrics by executing optimized circuits on a quantum computer or simulating them. Our results achieve comparable accuracy to previous research in quantum natural language processing, suggesting the potential of this approach in applications beyond quantum natural language processing. We also analyze the model&rsquo;s expressibility and entangling capability histograms for further insights.</p>
</description>
</item>
<item>
<title>Quantum Annealing Method for Dynamic Virtual Machine and Task Allocation in Cloud Infrastructures from A Sustainability Perspective</title>
<link>https://valteruo.github.io/publication/smdb23/</link>
<pubDate>Mon, 03 Apr 2023 01:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/publication/smdb23/</guid>
<description><p>I will update link to the paper after it is published.</p>
</description>
</item>
<item>
<title>Quantum Circuit Learning Method for SQL Cardinality, Cost and Time Estimation</title>
<link>https://valteruo.github.io/talk/quantum-circuit-learning-method-for-sql-cardinality-cost-and-time-estimation/</link>
<pubDate>Mon, 14 Nov 2022 08:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/talk/quantum-circuit-learning-method-for-sql-cardinality-cost-and-time-estimation/</guid>
<description><p>Structured Query Language (SQL) is the most used relational database query language in the world. In modern applications, data volume, variety, and connectivity have increased. Querying data should not become a bottleneck in data-intensive applications.</p>
<p>Query processors in relational databases can assign SQL queries to various measurements, such as cardinality, cost, and execution time estimations [1]. The optimization of the query heavily depends on the estimates. Usually, the problem is solved with machine learning, dynamic programming, or integer programming.
Quantum computers are reaching the level where they can be part of various applications on a small scale. We believe that databases and quantum computers will be co-designed in the future so that combinatorically hard database optimization problems can be solved efficiently and with high quality on quantum computers.
As far as we know, our work is the first attempt to apply quantum computing and quantum circuit learning as a part of the SQL query optimization pipeline. Even if we cannot beat the classical methods with the current quantum hardware, we can point out its limitations, understand quantum computing frameworks in a circuit learning context, and propose novel methods to model the problems with quantum computing algorithms. Especially quantum computing and machine learning are a promising combination because both are based on linear algebra and probability theory.</p>
<p>We utilize methods from quantum natural language processing (QNLP) [2] and quantum circuit learning [3]. First, we parse SQL queries and represent them using context-free grammar (CFG) diagrams. The CFG diagrams are functorially mapped to pregroup grammar diagrams. We perform a rewriting process for the pregroup grammar diagrams to optimize and reduce their size. We functorially translated them into parameterized quantum circuits. We will optimize the circuit parameters using standard quantum circuit learning pipelines. A quantum computer or a simulator is used to evaluate the circuit, but the actual training happens on classical hardware.</p>
<p>This is still ongoing work. Currently, we are at the phase where we can represent SELECT-FROM-WHERE type of SQL queries with complex filtering and join expressions using pregroup grammar diagrams and parametrized circuits. The corresponding results from QNLP are promising. We are excited to be able to express complex SQL queries as parametrized circuits and utilize the quantum circuit learning methods.</p>
<p>[1] Lan, H., Bao, Z. &amp; Peng, Y. A Survey on Advancing the DBMS Query Optimizer: Cardinality Estimation, Cost Model, and Plan Enumeration. Data Sci. Eng. 6, 86–101 (2021). <a href="https://doi.org/10.1007/s41019-020-00149-7" target="_blank" rel="noopener">https://doi.org/10.1007/s41019-020-00149-7</a>
[2] Meichanetzidis, K., Gogioso, S., De Felice, G., Chiappori, N., Toumi, A., &amp; Coecke, B. (2020). Quantum natural language processing on near-term quantum computers. arXiv preprint arXiv:2005.04147.
[3] Mitarai, K., Negoro, M., Kitagawa, M., &amp; Fujii, K. (2018). Quantum circuit learning. Physical Review A, 98(3), 032309.</p>
</description>
</item>
<item>
<title>Synergy between Quantum Computers and Databases</title>
<link>https://valteruo.github.io/publication/vldb22_phd_workshop/</link>
<pubDate>Mon, 05 Sep 2022 01:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/publication/vldb22_phd_workshop/</guid>
<description><p>A short paper that outlines my research connecting quantum computing, databases and category theory.</p>
</description>
</item>
<item>
<title>Studies on Solving Kakuro Puzzle with Grover's Algorithm</title>
<link>https://valteruo.github.io/post/classiq_coding_competition_kakuro/</link>
<pubDate>Sun, 26 Jun 2022 00:00:00 +0000</pubDate>
<guid>https://valteruo.github.io/post/classiq_coding_competition_kakuro/</guid>
<description><h2 id="introduction">Introduction</h2>
<p><a href="https://www.classiq.io/" target="_blank" rel="noopener">Classiq</a> organized an interesting quantum coding competition in May and June 2022. The competition consisted of four problems: <a href="https://www.classiq.io/competition/kakuro" target="_blank" rel="noopener">Kakuro</a>, <a href="https://www.classiq.io/competition/toffoli" target="_blank" rel="noopener">decomposing multi-control CNOT gate</a>, <a href="https://www.classiq.io/competition/hamiltonian" target="_blank" rel="noopener">Hamiltonian simulation</a> and <a href="https://www.classiq.io/competition/state-preparation" target="_blank" rel="noopener">log-normal state preparation</a>.</p>
<p>This blog post discusses my studies on the Kakuro problem, which should be solved with Grover&rsquo;s algorithm. This problem has been somewhat confusing. If we read the problem definition very carefully, it does not describe what kind of Grover algorithm application it should be. The question is if we are allowed to encode the constraints in Grover&rsquo;s oracle, which is, in my opinion, a lot easier task than writing an oracle that genuinely solves the puzzle.</p>
<p>I developed three different solutions to the challenge. Again, these solutions did not really meet the judging criteria and were not among the winning solutions. But instead, they study the problem from different angles.</p>
<p>First, I represent the solution that encodes the variables and values. Instead of using a massive number of qubits, we should design quantum algorithms to take advantage of the exponentially growing state space. Hilbert&rsquo;s space is indeed large, and we should benefit from that. The first solution&rsquo;s downside is that it is not precisely Grover&rsquo;s algorithm that solves the problem. In my opinion, Grover&rsquo;s algorithm instead reads quantumly encoded solution from the black box oracle circuit. The black box oracle circuit is constructed classically. The solution aligns with Grover&rsquo;s original idea of finding the marked element from an unsorted list. Now it depends on the problem definition if this is allowed or not. I developed the solution so you can input any Kakuro problem (not just the one in the Classiq&rsquo;s problem). Then the algorithm classically creates the oracle, and Grover&rsquo;s algorithm &lsquo;&lsquo;reads&rsquo;&rsquo; the solution from the oracle.</p>
<p>The second solution follows the idea that is also represented in Qiskit documentation: <a href="https://qiskit.org/textbook/ch-algorithms/grover.html#5.-Solving-Sudoku-using-Grover%27s-Algorithm-" target="_blank" rel="noopener">Solving Sudoku using Grover&rsquo;s Algorithm</a>. I believe that this is the idea that has been in the problem developers&rsquo; minds while designing the Kakuro challenge. Grover&rsquo;s algorithm solves the problem unlike the previous case, but the downside is the circuit&rsquo;s large qubit number and depth. On the other hand, this algorithm is not so interesting because it does not utilize the state space similarly to Grover&rsquo;s original solution.</p>
<p>I partly participate in the competitions because they motivate me to study new topics. Thus I also want to consider the third option based on an exciting paper: <a href="https://arxiv.org/abs/1912.04088" target="_blank" rel="noopener">Grover Adaptive Search for Constrained Polynomial Binary Optimization</a>. In this solution, I first encode the Kakuro problem as a quadratic unconstrained binary optimization (QUBO) problem, which could be solved with Grover&rsquo;s algorithm in sufficiently small instances. This is an exciting approach because it differs significantly from the other two solutions. On the other hand, Grover&rsquo;s Adaptive search does not scale well. I demonstrate that D-waves quantum annealers (and Amazon Braket) can solve the problem quickly (without Grover, unfortunately).</p>
<h2 id="kakuro-problem">Kakuro problem</h2>
<p>The problem is described well in <a href="https://www.classiq.io/competition/kakuro" target="_blank" rel="noopener">Classiq&rsquo;s problem description</a> and also in <a href="https://en.wikipedia.org/wiki/Kakuro" target="_blank" rel="noopener">Wikipedia</a>. The problem we solve here is assumed to have certain constraints that simplify the problem.</p>
<h2 id="transforming-integer-variables-to-binary-variables">Transforming integer variables to binary variables</h2>
<p>The notebook utilizes a specific binary variable encoding. Let us assume that we have $n$ variables $x_i$ for $i = 0, \ldots, n - 1$. Now each of these variables can hold a value $0,1,2,3$. In order to express to problem using the binary variables, we define binary variables $x_{i, j}$ so that $i = 0, \ldots, n - 1$ and $j \in \left\{0,1,2,3 \right\}$. The interpretation of the binary variables is simple: $x_{i,j} = 1$ if $x_i = j$. This enables us to encode integers into a binary model. We have $4n$ binary variables in the final model.</p>
<h2 id="section-1-solve-problem-classically-and-encode-solution-as-grovers-oracle">Section 1: Solve problem classically and encode solution as Grover&rsquo;s oracle</h2>
<p>The first idea to tackle the challenge is to assign a state for each binary variable $x_{i,j}$. In the end, if we measure $x_{i,j} = 1$, we know that $x_i = j$. For example, the variable $x_{0,0}$ can correspond to the state $|0000\rangle$ in the case $n = 4$. Because the number of states grows exponentially, we would be able to represent large problems with a small number of qubits. If we can transform the constraints into Grover oracle, which favors those states that correspond to the problem&rsquo;s solution, this solves the problem.</p>
<p>This exercise shows me an exciting and confusing point about quantum oracles. Usually, quantum oracles encode the problems that we are solving. Oracles are black-box. In that sense, in this solution proposal, the quantum computer is just a machine that we use to read the result from the oracle.</p>
<h3 id="studying-constraint-types">Studying constraint types</h3>
<p>We can divide the constraints into two classes. Because I want to be able to code the solution so that anyone, without thinking anything, simply inputs Kakuro constraints from the problem, I start by considering the simple example case from Classiq&rsquo;s problem definition:
$$
\begin{align}
x_0 \neq & x_1 &\quad x_0 \neq& x_2 \\
x_1 \neq & x_3 &\quad x_2 \neq& x_3 \\
x_0 + x_1 =& 3 &\quad x_0 + x_2 =& 3 \\
x_2 + x_3 =& 4 &\quad x_1 + x_3 =& 4.
\end{align}
$$
</p>
<h4 id="constraint-type-1-inequality-between-two-variables">Constraint type 1: inequality between two variables</h4>
<p>Obviously, the first class of constraints is
$$
\begin{align}
x_0 \neq & x_1 \\
x_2 \neq& x_3 \\
x_0 \neq& x_2 \\
x_1 \neq & x_3.
\end{align}
$$
</p>
<p>Let&rsquo;s focus on the first constraint $x_0 \neq x_1$. In the binary variable format, this constraint means that if $x_{i,j} = 1$ then $x_{k,j} = 0$ for $i = 0, 1$, $k = 0, 1$, $i \neq k$ and for $j = 0,1,2,3$. In other words, if we flip the value of the variable $x_{0,j}$ (meaning it is part of the solution), then we are not allowed to flip the variable $x_{1,j}$. If we were to flip the both variables, it would mean $x_0 = j = x_1$, which is not allowed. This analysis shows that we have multiple options for how to flip the binary values of the variables.</p>
<p>We initially create all the possible correct circuits for the constraint $x_0 \neq x_1$. Then we proceed to the next constraint $x_2 \neq x_3$ and append the possible options to the circuits produced in the previous step. At each appending phase, we check if some previous constraints are violated. If a constraint is violated, we drop the circuit from the process. Finally, we are left with Grover&rsquo;s oracles that produce the correct solution to the problem. Note that this process produces all the correct circuits; for this simple example, there are two.</p>
<h4 id="constraint-type-2-sum-of-variables-with-equality-to-constant">Constraint type 2: sum of variables with equality to constant</h4>
<p>Clearly, the second class of constraints is
$$
\begin{align}
x_0 + x_1 =& 3 \\
x_2 + x_3 =& 4 \\
x_0 + x_2 =& 3 \\
x_1 + x_3 =& 4.
\end{align}
$$
</p>
<p>Again, let&rsquo;s focus on the constraint $x_0 + x_1 = 3$. This means that we have two variables whose values sum up to three. We can divide this into multiple cases:</p>
<ul>
<li>$x_0 = 0$ and $x_1 = 3$</li>
<li>$x_0 = 3$ and $x_1 = 0$</li>
<li>$x_0 = 1$ and $x_1 = 2$</li>
<li>$x_0 = 2$ and $x_1 = 1$</li>
</ul>
<p>Luckily variables can hold numbers only up to three, so the maximum value we can face on the right-hand side of the equality is $6$. Also, it is safe to assume that we do not sum more than three variables at the time, and all the variables are different since they appear on the same row or column in the puzzle. This reduces the number of combinations.</p>
<p>Let&rsquo;s study the case that we want to encode $x_0 = 0$ and $x_1 = 3$. This means that we want to set $x_{0,0} = 1$ and $x_{1,3} = 1$ as binary variables. If we have encoded some constraints before this constraint, flipping the values of these variables might violate some previous constraints. That allows us to drop certain oracles from the construction process. Now we are only left to concretely code this approach.</p>
<h3 id="implementation-of-the-first-solution-proposal">Implementation of the first solution proposal</h3>
<pre><code class="language-python">from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import MCXGate, XGate, CCXGate, TGate
import numpy as np
import copy
</code></pre>
<p>If we concentrate on the main problem, the constraints without any simplification are
$$
\begin{align}
x_0 &\neq x_1 &\quad x_0 &\neq x_2 &\quad x_1 &\neq x_3 \\
x_1 &\neq x_5 &\quad x_2 &\neq x_3 &\quad x_2 &\neq x_4 \\
x_3 &\neq x_4 &\quad x_3 &\neq x_5 &\quad x_4 &\neq x_6 \\
x_5 &\neq x_6 &&&& \\
x_0 + x_2 &= 5 &\quad x_1 + x_3 + x_5 &= 3 &\quad x_4 + x_6 &= 1 \\
x_2 + x_3 + x_4 &= 5 &\quad x_0 + x_1 &= 3 &\quad x_5 + x_6 &= 1.
\end{align}
$$
</p>
<p>For four variables, we have $4*4 = 16$ binary variables, which requires $16$ states, so we need exactly four query qubits in Grover&rsquo;s algorithm. For six variables, we need five query qubits. The Classiq&rsquo;s Kakuro puzzle becomes</p>
<pre><code class="language-python">query_qubits = 5
n_variables = 7
inequality_constraints = [(0,1), (0,2), (1,3), (1,5), (2,3), (2,4), (3,4), (3,5), (4,6), (5,6)]
sum_equality_constraints = [{'variables': [0, 2], 'sum': 5},
{'variables': [1, 3, 5], 'sum': 3},
{'variables': [4, 6], 'sum': 1},
{'variables': [2, 3, 4], 'sum': 5},
{'variables': [0, 1], 'sum': 3},
{'variables': [5, 6], 'sum': 1}]
</code></pre>
<p>The following injective function maps the binary variables $x_{i,j}$ to integers whose binary representations correspond to states. We return the binary representation as a list since we will need to access each element in the presentation later.</p>
<pre><code class="language-python">def variable_to_state(i,j):
int_repr = 4*i + j
bin_repr = np.binary_repr(int_repr)
return '0'*(query_qubits - len(bin_repr)) + bin_repr
def state_to_variable(state):
decimal = int(state, 2)
j = decimal % 4
i = int(np.floor(decimal/4))
return (i, j)
</code></pre>
<pre><code class="language-python"># Encodes if x_3 = 2 i.e. x_3 = 2 is True if |1110&gt; is True after measurement
print(variable_to_state(3,2))
print(state_to_variable(variable_to_state(3,2)))
</code></pre>
<p>We proceed with the following idea: Every constraint induces options on how to flip phases. Whatever we include in the circuit, we need to exclude something. Consider the example if $x_{0,1} = 1$ then $x_{1,1} = 0$. When we include the gate that encodes the solution $x_{0,1} = 1$, then we cannot use the gate encoding $x_{1,1} = 1$ at any part of the circuit later. This process reduces the correct oracles.</p>
<pre><code class="language-python">def compose_phase_flip_gate(i, j, circuit):
ctrl = variable_to_state(i,j)
circuit.add(ctrl)
return circuit
def expand_inequality_oracles(x, y, oracles):
new_oracles = []
for oracle in oracles:
for j in range(4):
oracle_copy = copy.deepcopy(oracle)
bin_table = oracle_copy['bin_table']
if bin_table[x][j] != -1 and bin_table[y][j] != 1:
bin_table[x][j] = 1
for k in range(4):
if k != j:
bin_table[x][k] = -1
bin_table[y][j] = -1
circuit = oracle_copy['circuit']
compose_phase_flip_gate(x, j, circuit)
new_oracles.append(oracle_copy)
return new_oracles
def initialize_oracles(x, y, oracles):
for j in range(4):
circuit = compose_phase_flip_gate(x, j, set())
bin_table = np.zeros(shape=(n_variables, 4))
bin_table[x][j] = 1
for k in range(4):
if k != j:
bin_table[x][k] = -1
bin_table[y][j] = -1
oracles.append({'circuit': circuit, 'bin_table' : bin_table })
return oracles
def create_inequality_oracles(constraints):
oracles = []
x = constraints[0][0]
y = constraints[0][1]
oracles = initialize_oracles(x, y, oracles)
oracles = initialize_oracles(y, x, oracles)
for constraint in constraints[1:]:
x = constraint[0]
y = constraint[1]
oracles = expand_inequality_oracles(x, y, oracles)
oracles = expand_inequality_oracles(y, x, oracles)
return oracles
</code></pre>
<p>We run the code:</p>
<pre><code class="language-python">oracles = create_inequality_oracles(inequality_constraints)
print(len(oracles))
</code></pre>
<p>The following function calculates how many ways we can sum two or three variables to obtain the given sum. We can exclude cases when there are two equal numbers in the sum since these cases will always violate some inequality constraints.</p>
<pre><code class="language-python">def sum_options(constraint):
variables = constraint['variables']
sum_value = constraint['sum']
result = []
if len(variables) == 2:
for i in range(4):
if sum_value - i &lt; 4 and sum_value - i &gt; -1:
if i != sum_value - i:
result.append((i, sum_value - i))
elif len(variables) == 3:
for i in range(4):
if sum_value - i &lt; 4 and sum_value - i &gt; -1:
options = sum_options({'variables': [0, 1], 'sum': i})
for o in options:
if (sum_value - i) != o[0] and (sum_value - i) != o[1] and o[0] != o[1]:
result.append((sum_value - i, o[0], o[1]))
result.append((o[0], sum_value - i, o[1]))
result.append((o[0], o[1], sum_value - i))
return list(set(result))
</code></pre>
<pre><code class="language-python">print(sum_options({'variables': [2, 3, 4], 'sum': 3}))
print(sum_options({'variables': [2, 3, 4], 'sum': 4}))
print(sum_options({'variables': [2, 3, 4], 'sum': 5}))
print(sum_options({'variables': [2, 3, 4], 'sum': 6}))
print(sum_options({'variables': [0, 1], 'sum': 1}))
print(sum_options({'variables': [0, 1], 'sum': 2}))
print(sum_options({'variables': [0, 1], 'sum': 3}))
print(sum_options({'variables': [0, 1], 'sum': 4}))
print(sum_options({'variables': [0, 1], 'sum': 5}))
</code></pre>
<p>Output:</p>
<pre><code class="language-python">[(0, 2, 1), (1, 2, 0), (2, 1, 0), (2, 0, 1), (0, 1, 2), (1, 0, 2)]
[(0, 3, 1), (0, 1, 3), (3, 1, 0), (1, 0, 3), (1, 3, 0), (3, 0, 1)]
[(3, 2, 0), (0, 2, 3), (3, 0, 2), (2, 0, 3), (0, 3, 2), (2, 3, 0)]
[(1, 3, 2), (1, 2, 3), (2, 1, 3), (3, 2, 1), (3, 1, 2), (2, 3, 1)]
[(0, 1), (1, 0)]
[(0, 2), (2, 0)]
[(1, 2), (0, 3), (2, 1), (3, 0)]
[(3, 1), (1, 3)]
[(2, 3), (3, 2)]
</code></pre>
<p>Finally, we need to encode the sum constraints. We take the previously generated oracles and modify them. If some constraints are violated, we drop the oracle from the process. Finally, we are left with all the correct oracles that encode the solution to the Kakuro problem.</p>
<pre><code class="language-python">def limit_oracles_based_on_sum_constraints(sums, constraint, oracles):
correct_oracles = []
variables = constraint['variables']
for oracle in oracles:
for option in sums:
bin_table = oracle['bin_table']
if all([bin_table[variables[k]][option[k]] == 1 for k in range(len(variables))]):
correct_oracles.append(oracle)
return correct_oracles
def build_sum_oracle(constraint, oracles):
sums = sum_options(constraint)
oracles = limit_oracles_based_on_sum_constraints(sums, constraint, oracles)
return oracles
</code></pre>
<pre><code class="language-python">for constraint in sum_equality_constraints:
oracles = build_sum_oracle(constraint, oracles)
#print(len(oracles))
</code></pre>
<p>The code produces some duplicates which can be removed:</p>
<pre><code class="language-python">def remove_duplicates(oracles):
new_oracles = [oracles[0]]
for oracle in oracles[1:]:
if not any([np.array_equal(oracle['bin_table'], oracle2['bin_table']) for oracle2 in new_oracles]):
new_oracles.append(oracle)
return new_oracles
</code></pre>
<pre><code class="language-python">oracles = remove_duplicates(oracles)
</code></pre>
<h3 id="implement-grover-search">Implement Grover search</h3>
<p>Next, we code the standard Grover search.</p>
<pre><code class="language-python">import matplotlib.pyplot as plt
import numpy as np
from qiskit import IBMQ, Aer, assemble, transpile
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.visualization import plot_histogram
</code></pre>
<pre><code class="language-python">def initialize(qc, qubits):
qc.x(qubits[-1])
qc.h(qubits)
return qc
</code></pre>
<p>The following function creates the oracle for the correct states.</p>
<pre><code class="language-python">def compose_grover_oracle(qc, qubits, ctrl_char):
not_indices = []
qc.barrier()
for i, b in enumerate(ctrl_char):
if b == '0':
qc.x(len(qubits) - 2 - i)
not_indices.append(len(qubits) - 2 - i)
qc.mcx(list(qubits[:-1]), len(qubits) - 1)
for i in not_indices:
qc.x(i)
return qc
</code></pre>
<pre><code class="language-python">def diffusion(qc, qubits):
qc.barrier()
qc.h(qubits)
qc.x(qubits[:-1])
qc.mcx(list(qubits[:-1]), len(qubits) - 1)
qc.x(qubits[:-1])
qc.h(qubits)
return qc
</code></pre>
<p>The algorithm provides all the possible answers to the puzzle. The main Kakuro seems to have just a single solution, but the other example has two solutions. Both are run on a noisy simulator. In both cases, just one oracle call is sufficient.</p>
<pre><code class="language-python">final_circuits = []
for oracle in oracles:
grover_circuit = QuantumCircuit(query_qubits + 1, query_qubits)
grover_circuit = initialize(grover_circuit, range(query_qubits + 1))
for ctrl_char in oracle['circuit']:
grover_circuit = compose_grover_oracle(grover_circuit, range(query_qubits + 1), ctrl_char)
grover_circuit = diffusion(grover_circuit, range(query_qubits + 1))
final_circuits.append(grover_circuit)
final_circuits[0].draw(output='latex')
</code></pre>
<p>Output:
<figure >
<div class="d-flex justify-content-center">
<div class="w-100" ><img alt="kakuro1" srcset="
/post/classiq_coding_competition_kakuro/figure1_hu43558ef56fc0ea93fb961d9f6487769a_21037_acd8e3b7f728641e8a60181b493f7c09.webp 400w,
/post/classiq_coding_competition_kakuro/figure1_hu43558ef56fc0ea93fb961d9f6487769a_21037_69f95e7a5965191f57c53dc71fb57be2.webp 760w,
/post/classiq_coding_competition_kakuro/figure1_hu43558ef56fc0ea93fb961d9f6487769a_21037_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://valteruo.github.io/post/classiq_coding_competition_kakuro/figure1_hu43558ef56fc0ea93fb961d9f6487769a_21037_acd8e3b7f728641e8a60181b493f7c09.webp"
width="760"
height="108"
loading="lazy" data-zoomable /></div>
</div></figure>
</p>
<pre><code class="language-python">solution_counts = []
for grover_circuit in final_circuits:
grover_circuit.measure(range(query_qubits), range(query_qubits))
aer_sim = Aer.get_backend('aer_simulator', )
qobj = assemble(grover_circuit)
result = aer_sim.run(qobj, shots=100000).result()
counts = result.get_counts()
solution_counts.append(counts)
legend = [str(i + 1) + '. execution' for i in range(len(solution_counts))]
plot_histogram(solution_counts, legend=legend, sort='asc', figsize=(15,12))
</code></pre>
<p>Output:
<figure >
<div class="d-flex justify-content-center">
<div class="w-100" ><img alt="kakuro2" srcset="
/post/classiq_coding_competition_kakuro/figure2_hu350956b9fa2e7866c34477466f9687f7_28705_f078d4c61ac845a7544a4621e39ea4e7.webp 400w,
/post/classiq_coding_competition_kakuro/figure2_hu350956b9fa2e7866c34477466f9687f7_28705_a684774ff75f4f88c8ea6a80628bee17.webp 760w,
/post/classiq_coding_competition_kakuro/figure2_hu350956b9fa2e7866c34477466f9687f7_28705_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://valteruo.github.io/post/classiq_coding_competition_kakuro/figure2_hu350956b9fa2e7866c34477466f9687f7_28705_f078d4c61ac845a7544a4621e39ea4e7.webp"
width="760"
height="526"
loading="lazy" data-zoomable /></div>
</div></figure>
</p>
<pre><code class="language-python">for counts in solution_counts:
sorted_counts = dict(sorted(counts.items(), key = lambda item: item[1], reverse=True))
counts_iterator = iter(sorted_counts)
print(&quot;Possible solution to the Kakuro puzzle: &quot;)
for i in range(n_variables):
b = next(counts_iterator)
(i, j) = state_to_variable(b)
print(&quot;x_&quot; + str(i) + &quot; = &quot; + str(j))
print()
</code></pre>
<p>Output:</p>
<pre><code class="language-python">A possible solution to the Kakuro puzzle:
x_4 = 0
x_3 = 2
x_1 = 1
x_6 = 1
x_0 = 2
x_2 = 3
x_5 = 0
</code></pre>
<h3 id="decompose-mcx-gates-for-the-actual-kakuro-puzzle">Decompose MCX gates for the actual Kakuro puzzle</h3>
<p>The part of the solution can be skipped if you are not interested in reading about gate decompositions.</p>
<p>The solution above is easy to understand using multi-control Toffoli gates. Still, since we need to follow the original Classiq&rsquo;s problem definition, we must decompose the multi-control Toffoli gates into single qubit and CNOT gates. The evaluation of the circuit is based on the number of the single qubit and CNOT gates. The decomposition is something we already studied in another Classiq&rsquo;s challenge. Since the number of ancilla qubits is not restricted now, we can use <a href="https://algassert.com/quirk#circuit=%7b%22cols%22:[[%22X%22,%22X%22,%22X%22,%22X%22,%22X%22],[%22%E2%80%A2%22,%22%E2%80%A2%22,1,1,1,1,%22X%22],[1,1,%22%E2%80%A2%22,%22%E2%80%A2%22,1,1,1,%22X%22],[1,1,1,1,%22%E2%80%A2%22,1,1,1,%22X%22],[1,1,1,1,1,1,%22%E2%80%A2%22,1,1,%22X%22],[1,1,1,1,1,1,1,%22%E2%80%A2%22,%22%E2%80%A2%22,1,%22X%22],[1,1,1,1,1,%22X%22,1,1,1,%22%E2%80%A2%22,%22%E2%80%A2%22],[1,1,1,1,1,1,1,%22%E2%80%A2%22,%22%E2%80%A2%22,1,%22X%22],[1,1,1,1,1,1,%22%E2%80%A2%22,1,1,%22X%22],[1,1,1,1,%22%E2%80%A2%22,1,1,1,%22X%22],[1,1,%22%E2%80%A2%22,%22%E2%80%A2%22,1,1,1,%22X%22],[%22%E2%80%A2%22,%22%E2%80%A2%22,1,1,1,1,%22X%22]]%7d" target="_blank" rel="noopener">this simple decomposition</a>. In the decomposition we still decompose Toffoli-gates. This produces the following code.</p>
<pre><code class="language-python">def toffoli_decomposed(qc, ctrl_qubits, target):
first = ctrl_qubits[0]
second = ctrl_qubits[1]
# I took this decomposition from my solution to multi-control Toffoli challenge
qc.h(target)
qc.cx(second, target)
qc.append(TGate().inverse(), [target])
qc.cx(first, target)
qc.append(TGate(), [target])
qc.cx(first, second)
qc.cx(second, target)
qc.cx(first, target)
qc.append(TGate().inverse(), [second])
qc.append(TGate().inverse(), [target])
qc.cx(first, target)
qc.cx(first, second)
qc.append(TGate(), [target])
qc.append(TGate(), [second])
qc.append(TGate(), [first])
qc.h(target)
return qc
# This function decomposes Toffoli gate with 5-control qubits
def mcx_5_decomposed(qc, ctrl_qubits, target, ancilla):
c0, c1, c2 = ctrl_qubits[0], ctrl_qubits[1], ctrl_qubits[2]
c3, c4 = ctrl_qubits[3], ctrl_qubits[4]
qc = toffoli_decomposed(qc, [c0, c1], ancilla[0])
#qc.mcx([c0, c1], ancilla[0])
qc = toffoli_decomposed(qc, [c2, c3], ancilla[1])
#qc.mcx([c2, c3], ancilla[1])
qc.cnot(c4, ancilla[2])
qc.cnot(ancilla[0], ancilla[3])
qc = toffoli_decomposed(qc, [ancilla[1], ancilla[2]], ancilla[4])
#qc.mcx([ancilla[1], ancilla[2]], ancilla[4])
qc = toffoli_decomposed(qc, [ancilla[3], ancilla[4]], target[0])
#qc.mcx([ancilla[3], ancilla[4]], target[0])
qc = toffoli_decomposed(qc, [ancilla[1], ancilla[2]], ancilla[4])
#qc.mcx([ancilla[1], ancilla[2]], ancilla[4])
qc.cnot(ancilla[0], ancilla[3])
qc.cnot(c4, ancilla[2])
qc = toffoli_decomposed(qc, [c2, c3], ancilla[1])
#qc.mcx([c2, c3], ancilla[1])
qc = toffoli_decomposed(qc, [c0, c1], ancilla[0])
#qc.mcx([c0, c1], ancilla[0])
return qc
</code></pre>
<pre><code class="language-python">def compose_grover_oracle2(qc, query_qubits, target_qubit, ancilla_qubits, ctrl_char):
not_indices = []
qc.barrier()
for i, b in enumerate(ctrl_char):
if b == '0':
qc.x(query_qubits[len(query_qubits) - i - 1])
not_indices.append(len(query_qubits) - i - 1)
mcx_5_decomposed(qc, query_qubits, target_qubit, ancilla_qubits)
for i in not_indices:
qc.x(query_qubits[i])
return qc
</code></pre>
<pre><code class="language-python">if query_qubits == 5:
final_circuits = []
for oracle in oracles:
ancialla_reg = QuantumRegister(5, name = 'ancilla')
query_reg = QuantumRegister(query_qubits, name = 'query')
target_reg = QuantumRegister(1, name = 'target')
classical_reg = ClassicalRegister(query_qubits)
grover_circuit = QuantumCircuit(query_reg, target_reg, ancialla_reg, classical_reg)
grover_circuit.h(query_reg)
grover_circuit.x(target_reg)
grover_circuit.h(target_reg)
for ctrl_char in oracle['circuit']:
grover_circuit = compose_grover_oracle2(grover_circuit, query_reg, target_reg, ancialla_reg, ctrl_char)
grover_circuit.barrier()
grover_circuit.h(query_reg)
grover_circuit.h(target_reg)
grover_circuit.x(query_reg)
grover_circuit.mcx(query_reg, target_reg[0])
grover_circuit.x(query_reg)
grover_circuit.h(query_reg)
grover_circuit.h(target_reg)
final_circuits.append(grover_circuit)
</code></pre>
<p>The following code visualizes the circuit, but now it is quite long.</p>
<pre><code class="language-python">final_circuits[0].draw(output = 'mpl')
</code></pre>
<p>Then we run the code on the simulator and ensure that the decomposition is correct.</p>
<pre><code class="language-python">solution_counts = []
for grover_circuit in final_circuits:
grover_circuit.measure(range(query_qubits), range(query_qubits))
aer_sim = Aer.get_backend('aer_simulator')
qobj = assemble(grover_circuit)
result = aer_sim.run(qobj, shots=100000).result()
counts = result.get_counts()
solution_counts.append(counts)
legend = [str(i + 1) + '. execution' for i in range(len(solution_counts))]
plot_histogram(solution_counts, legend=legend, sort='asc', figsize=(15,12))
</code></pre>
<p>Output:
<figure >
<div class="d-flex justify-content-center">
<div class="w-100" ><img alt="kakuro3" srcset="
/post/classiq_coding_competition_kakuro/figure3_hucb1f40c29e8b69a98026f380a3b0a21c_29030_6b7470bfafd593dbe7a2bfc93a572fde.webp 400w,
/post/classiq_coding_competition_kakuro/figure3_hucb1f40c29e8b69a98026f380a3b0a21c_29030_3c676cf3c0b9c5d32c6c112981e53a92.webp 760w,
/post/classiq_coding_competition_kakuro/figure3_hucb1f40c29e8b69a98026f380a3b0a21c_29030_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://valteruo.github.io/post/classiq_coding_competition_kakuro/figure3_hucb1f40c29e8b69a98026f380a3b0a21c_29030_6b7470bfafd593dbe7a2bfc93a572fde.webp"
width="760"
height="526"
loading="lazy" data-zoomable /></div>
</div></figure>
</p>
<pre><code class="language-python">for counts in solution_counts:
sorted_counts = dict(sorted(counts.items(), key = lambda item: item[1], reverse=True))
counts_iterator = iter(sorted_counts)
print(&quot;Possible solution to the Kakuro puzzle: &quot;)
for i in range(n_variables):
b = next(counts_iterator)
(i, j) = state_to_variable(b)
#print(i,j)
print(&quot;x_&quot; + str(i) + &quot; = &quot; + str(j))
print()
</code></pre>
<p>Output:</p>
<pre><code class="language-python">A possible solution to the Kakuro puzzle:
x_5 = 0
x_6 = 1
x_4 = 0
x_3 = 2
x_0 = 2
x_2 = 3
x_1 = 1
</code></pre>
<p>Statistics about the final decomposed circuit. We can now easily count the 371 CNOT gates in the oracle.</p>
<pre><code class="language-python">final_circuits[0].qasm(filename = 'kakuro_qasm')
print('Gate counts: ', final_circuits[0].count_ops())
print('Depth: ', final_circuits[0].depth())
</code></pre>
<p>Output:</p>
<pre><code class="language-python">Gate counts: OrderedDict([('cx', 371), ('t', 196), ('tdg', 147), ('h', 116), ('x', 51), ('barrier', 8), ('measure', 5), ('mcx_gray', 1)])
Depth: 371
</code></pre>
<h2 id="section-2-true-quantum-solution-with-idea-from-qiskit-documentation">Section 2: True quantum solution with idea from Qiskit documentation</h2>
<p>In this case, we encode the problem using the same binary variables. Following the idea in the <a href="https://qiskit.org/textbook/ch-algorithms/grover.html#sudoku" target="_blank" rel="noopener">Qiskit documentation</a>, we can note that the fact $x_0 \neq x_1$ in decimals is equivalent to the fact $x_{0,j} \neq x_{1,j}$ for all $j = 0,1,2,3$ in binary variables. The documentation gives us the following simple circuit that encodes this inequality for each $j = 0, 1, 2, 3$. The idea is that the ancilla qubit is true if one of the variables is true. If both are true, then the ancilla is false.</p>
<pre><code class="language-python">qr = QuantumRegister(2, 'x')
anc = QuantumRegister(1, 'ancilla')
circuit = QuantumCircuit(qr, anc)
circuit.cx(0, 2)
circuit.cx(1, 2)
circuit.draw(output='latex')
</code></pre>
<p>Output:
<figure >
<div class="d-flex justify-content-center">
<div class="w-100" ><img alt="kakuro4" srcset="
/post/classiq_coding_competition_kakuro/figure4_hue56e43bd623d64235ff69d1e41bacd94_3527_2747ad2f32de05977a9dd9534373b4df.webp 400w,
/post/classiq_coding_competition_kakuro/figure4_hue56e43bd623d64235ff69d1e41bacd94_3527_e5a71189fa5bd48bea88285b0025dfff.webp 760w,
/post/classiq_coding_competition_kakuro/figure4_hue56e43bd623d64235ff69d1e41bacd94_3527_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://valteruo.github.io/post/classiq_coding_competition_kakuro/figure4_hue56e43bd623d64235ff69d1e41bacd94_3527_2747ad2f32de05977a9dd9534373b4df.webp"
width="202"
height="86"
loading="lazy" data-zoomable /></div>
</div></figure>
</p>
<p>For simplicity, let us concentrate on Classiq&rsquo;s specific Kakuro puzzle and not work as general level as in the first case. I used all ten constraints in the below case without manually reducing the number. In the ancilla qubits, the first index (0-9) refers to the inequality constraint, and the second index refers to the value restricting the constraint.</p>
<p>Then we encode the sums. The idea behind sums is the following: for example, variables $x_2$, $x_3$, $x_4$ can be summed to $5$ with $x_2 = 2$, $x_3 = 3$, $x_4 = 0$. This means that we have the valid case where the binary variables $x_{2,2} = 1$, $x_{3,3} = 1$ and $x_{4,0} = 1$. Each of these binary variables corresponds to a qubit in the above circuit. Thus we can encode this constraint by adding a 3-control qubit Toffoli gate controlled by the qubits corresponding to the variables $x_{2,2}$, $x_{3,3}$ and $x_{4,0}$. That process creates the following circuit. When we mirror the gates to the other side of the last multi-control CNOT gate, we obtain the circuit that works as Grover&rsquo;s oracle. Unfortunately, it is again too large that we could execute it. This approach feels like a waste of qubits, but I cannot develop how to encode similar ideas to phases and use states as in the first solution proposal.</p>
<pre><code class="language-python">import pennylane as qml
from pennylane import numpy as np
sums = []
for c in sum_equality_constraints:
sums += sum_options(c)
variable_qubits = ['x' + str(i) + str(j) + ' ' for i in range(n_variables) for j in range(4)]
inequality_ancillas = ['i_anc' + str(i) + str(j) + ' ' for i in range(len(inequality_constraints)) for j in range(4)]
sum_ancillas = ['s_anc' + str(i) + ' ' for i in range(len(sums))]
n_ancilla = len(inequality_ancillas) + len(sum_ancillas)
workwires_for_pennylane = ['qml_wire' + str(i) + ' ' for i in range(10)]
targets = variable_qubits + inequality_ancillas + sum_ancillas+ ['main_anc'] + workwires_for_pennylane
# The circuit is so large that Pennylane's built-in devices did not seem to work
# because they are creating some large matrices. Maybe this can be avoided.
# Anyway, Cirq and Braket seem to work fine
dev = qml.device('cirq.simulator', wires = targets)
#dev = qml.device('braket.local.qubit', wires = targets)
def variables_to_wires(i, j):
return 4*i + j
def inequality_constraint_circuit(inequality_constraints):
for i, c in enumerate(inequality_constraints):
x, y = c[0], c[1]
for j in range(4):
x_wire = variables_to_wires(x, j)
y_wire = variables_to_wires(y, j)
target_ancilla = inequality_ancillas[4*i + j]
qml.CNOT(wires = [variable_qubits[x_wire], target_ancilla])
qml.CNOT(wires = [variable_qubits[y_wire], target_ancilla])
def inequality_constraint_circuit_reverse(inequality_constraints):
for i, c in reversed(list(enumerate(inequality_constraints))):
x, y = c[0], c[1]
for j in reversed(range(4)):
x_wire = variables_to_wires(x, j)
y_wire = variables_to_wires(y, j)
target_ancilla = inequality_ancillas[4*i + j]
qml.CNOT(wires = [variable_qubits[x_wire], target_ancilla])
qml.CNOT(wires = [variable_qubits[y_wire], target_ancilla])
def sum_constraint_circuit(sum_equality_constraints):
ancilla_index = 0
for c in sum_equality_constraints:
sums = sum_options(c)
variables = c['variables']
for sum_option in sums:
controls = [variable_qubits[variables_to_wires(variables[k], sum_option[k])] for k in range(len(variables))]
target_ancilla = sum_ancillas[ancilla_index]
ancilla_index += 1
qml.MultiControlledX(wires = controls + [target_ancilla], work_wires = workwires_for_pennylane[:2])
def sum_constraint_circuit_reverse(sum_equality_constraints):
ancilla_index = len(sum_ancillas) - 1
for c in reversed(sum_equality_constraints):
sums = sum_options(c)
variables = c['variables']
for sum_option in reversed(sums):
controls = [variable_qubits[variables_to_wires(variables[k], sum_option[k])] for k in range(len(variables))]
target_ancilla = sum_ancillas[ancilla_index]
ancilla_index -= 1
qml.MultiControlledX(wires = controls + [target_ancilla], work_wires = workwires_for_pennylane[2:4])
@qml.qnode(dev)
def full_grover_oracle(inequality_constraints, sum_equality_constraints):
inequality_constraint_circuit(inequality_constraints)
sum_constraint_circuit(sum_equality_constraints)
qml.MultiControlledX(wires = inequality_ancillas + sum_ancillas + ['main_anc'])
sum_constraint_circuit_reverse(sum_equality_constraints)
inequality_constraint_circuit_reverse(inequality_constraints)
return qml.state()
qml.drawer.use_style('black_white')
fig, ax = qml.draw_mpl(full_grover_oracle, show_all_wires = False)(inequality_constraints, sum_equality_constraints)
fig.set_size_inches(fig.get_size_inches()[0]*0.3, fig.get_size_inches()[1]*0.3)
</code></pre>
<p>Output:
<figure >
<div class="d-flex justify-content-center">
<div class="w-100" ><img alt="kakuro5" srcset="
/post/classiq_coding_competition_kakuro/figure5_hue09a58044857ba687a84b0b2fc8aadcb_326348_908f0a11d67b87d5c1da330f80d50262.webp 400w,
/post/classiq_coding_competition_kakuro/figure5_hue09a58044857ba687a84b0b2fc8aadcb_326348_ba262a0f9a4d16e8a7379cc343410f66.webp 760w,
/post/classiq_coding_competition_kakuro/figure5_hue09a58044857ba687a84b0b2fc8aadcb_326348_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://valteruo.github.io/post/classiq_coding_competition_kakuro/figure5_hue09a58044857ba687a84b0b2fc8aadcb_326348_908f0a11d67b87d5c1da330f80d50262.webp"
width="760"
height="335"
loading="lazy" data-zoomable /></div>
</div></figure>
</p>
<p>Next, we can insert the previous Grover oracle in Grover&rsquo;s algorithm. One solution to the simulation problem might be to note that the oracle does not implement anything which necessarily requires quantum: it is just a sequence of if-then clauses that flip bits. In this sense, we could take some states (possibly those which we consider relevant: the correct solution and a bunch of false solutions) from the initial distribution and then run the oracle with them. If these states indicate that the predicted right solution has a higher probability of being true after a measurement, we could say that the circuit is correct with high probability.</p>
<pre><code class="language-python">def apply_grover_oracle():
inequality_constraint_circuit(inequality_constraints)
sum_constraint_circuit(sum_equality_constraints)
qml.MultiControlledX(wires = inequality_ancillas + sum_ancillas + ['main_anc'], work_wires = workwires_for_pennylane[4:8])
sum_constraint_circuit_reverse(sum_equality_constraints)
inequality_constraint_circuit_reverse(inequality_constraints)
def apply_diffusion():
qml.broadcast(qml.Hadamard, variable_qubits + ['main_anc'], pattern = 'single')
qml.MultiControlledX(control_wires = variable_qubits, wires = ['main_anc'], control_values = '0'*(len(variable_qubits)), work_wires = workwires_for_pennylane[8:10])
qml.broadcast(qml.Hadamard, variable_qubits + ['main_anc'], pattern = 'single')
@qml.qnode(dev)
def full_grover2():
qml.PauliX(wires = ['main_anc'])
qml.broadcast(qml.Hadamard, variable_qubits + ['main_anc'], pattern = 'single')
apply_grover_oracle()
apply_diffusion()
return qml.probs(wires = variable_qubits)
</code></pre>
<p>The following code draws the whole Grover search circuit:</p>
<pre><code class="language-python">qml.drawer.use_style('black_white')
fig, ax = qml.draw_mpl(full_grover2, show_all_wires = False)()
fig.set_size_inches(fig.get_size_inches()[0]*0.3, fig.get_size_inches()[1]*0.3)
print(full_grover2())
</code></pre>
<h2 id="section-3-encode-the-problem-as-qubo-and-solve-it-with-grovers-adaptive-search">Section 3: Encode the problem as QUBO and solve it with Grover&rsquo;s Adaptive search</h2>
<h3 id="encode-constraints-as-qubo">Encode constraints as QUBO</h3>
<p>Again, we encode the variables and values using the binary variables $x_{i,j}$. The idea is to define an objective function
$$
f(x) = \sum_{i = 1}^{n}\sum_{j = i + 1}^{n}a_{i,j}x_ix_j + \sum_{i = 1}^{n}b_ix_i,
$$
where $x_i \in \left\{0, 1\right\}$ and $a_{i,j}, b_i\in R$ for $1 \leq i,j \leq n$. When the objective function is constructed to encode the constraints of the Kakuro problem, it heuristically solves the problem. The easiest method to solve the objective is to use quantum annealing but based on the paper <a href="https://arxiv.org/abs/1912.04088" target="_blank" rel="noopener">Grover Adaptive Search for Constrained Polynomial Binary Optimization</a>, we should be able to solve the problem with a novel application of Grover Search. Since QUBOs are strongly connected to quantum annealing, the most convenient framework to use here is D-wave&rsquo;s Ocean package. We can first test on a quantum annealer that the constructed QUBO encodes the correct problem.</p>
<p>First, some imports and helper functions:</p>
<pre><code class="language-python">import dimod
from dimod.generators.constraints import combinations as qubo_combinations
from dwave.system import LeapHybridSampler
from hybrid.reference import KerberosSampler
from dwave.system.composites import EmbeddingComposite
from braket.aws import AwsDevice
from braket.ocean_plugin import BraketSampler, BraketDWaveSampler
def append_linear_safe(var, val, linear):
if var in linear.keys():
linear[var] = linear[var] + val
else:
linear[var] = val
def append_quadratic_safe(var, val, quadratic):
if var in quadratic.keys():
quadratic[var] = quadratic[var] + val
else:
quadratic[var] = val
</code></pre>
<h4 id="objective-function-for-inequality-constraints">Objective function for inequality constraints</h4>
<p>For example, the constraint $x_0 \neq x_1$ means that for any $j = 0,1,2,3$, if $x_{0,j} = 1$ then $x_{1,j} = 0$. In this case the objective function reaches the correct minimum when we add term $x_{0,j}x_{1,j}$ for each $j = 0,1,2,3$. Thus we construct of QUBO which consists of terms as $x_{0,j}x_{1,j}$ encoding the inequality constraints (constraint 1).</p>
<pre><code class="language-python">def construct_bqm_constraint1(bqm, inequality_constraints):
vartype = dimod.BINARY
A = 1
linear = dict()
quadratic = dict()
offset = 0.0
for c in inequality_constraints:
x, y = c[0], c[1]
for j in range(4):
var1, var2 = (x,j), (y,j)
append_quadratic_safe((var1, var2), 1, quadratic)
bqm_c1 = dimod.BinaryQuadraticModel(linear, quadratic, offset, vartype)
bqm_c1.scale(A)
bqm.update(bqm_c1)
return bqm
</code></pre>
<h4 id="objective-function-for-sum-constraints">Objective function for sum constraints</h4>
<p>The following formulation follows the facts on how we can divide the sum constraints based on the <code>sum_options</code> function. Designing this objective function requires manual work because the squared terms need to be opened before we can input them.</p>
<pre><code class="language-python">from itertools import combinations
def construct_bqm_constraint2(bqm, sum_equality_constraints):
vartype = dimod.BINARY
A = 10
linear = dict()
quadratic = dict()
offset = 4
for c in sum_equality_constraints:
bin_variables = []
variables = c['variables']
options = sum_options(c)
for i, var in enumerate(variables):
for o in options:
bin_variables.append((var, o[i]))
offset = 4
linear_coefficient = -3
if len(variables) == 3:
offset = 36
linear_coefficient = -11
for b in bin_variables:
append_linear_safe(b, linear_coefficient, linear)
for comb in combinations(bin_variables, 2):
append_quadratic_safe(comb, 2, quadratic)
bqm_c2 = dimod.BinaryQuadraticModel(linear, quadratic, offset, vartype)
bqm_c2.scale(A)
bqm.update(bqm_c2)
return bqm
</code></pre>
<h4 id="additional-constraints">Additional constraints</h4>
<p>For every $i$ there is exactly one $j$ so that $x_{i,j} = 1$.</p>
<pre><code class="language-python">def construct_bqm_constraint3(bqm, n_variables):
strength = 40.0
for x in range(n_variables):
variables = [(x, j) for j in range(4)]
bqm3 = qubo_combinations(variables, 1, strength=strength)
bqm.update(bqm3)
return bqm
</code></pre>
<h3 id="demonstration-of-minimizing-objective-function-with-amazon-braket-d-wave-leap-and-local-machine">Demonstration of minimizing objective function with Amazon Braket, D-wave Leap, and local machine</h3>
<pre><code class="language-python">vartype = dimod.BINARY
bqm = dimod.BinaryQuadraticModel({}, {}, 0.0, vartype)
bqm = construct_bqm_constraint1(bqm, inequality_constraints)
bqm = construct_bqm_constraint2(bqm, sum_equality_constraints)
bqm = construct_bqm_constraint3(bqm, n_variables)
</code></pre>
<pre><code class="language-python">def solve_bqm_in_Amazon_Braket(bqm, system = &quot;Advantage&quot;):
device = None
num_reads = 10000
if system == &quot;Advantage&quot;:
device = &quot;arn:aws:braket:::device/qpu/d-wave/Advantage_system4&quot;
elif system == &quot;2000Q&quot;:
device = &quot;arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6&quot;
sampler = BraketDWaveSampler(device_arn = device)
sampler = EmbeddingComposite(sampler)
sampleset = sampler.sample(bqm, num_reads=num_reads)
sample = sampleset.first.sample
# print timing info for the previous D-Wave job
print(sampleset.info['additionalMetadata']['dwaveMetadata']['timing'])
return sample
</code></pre>
<pre><code class="language-python">def solve_exactly(bqm):
sampler = dimod.ExactSolver()
sampleset = sampler.sample(bqm)
sample = sampleset.first.sample
return sample
</code></pre>
<p>Now we hopefully get the correct result. We run the code on Amazon Braket:</p>
<pre><code class="language-python">result = solve_bqm_in_Amazon_Braket(bqm)
for elem in result:
if result[elem] == 1:
print('x_' + str(elem[0]) + ' = ' + str(elem[1]))
</code></pre>
<p>Output:</p>
<pre><code class="language-python">{'qpuSamplingTime': 1429400, 'qpuAnnealTimePerSample': 20, 'qpuAccessTime': 1444466, 'qpuAccessOverheadTime': 10467, 'qpuReadoutTimePerSample': 102, 'qpuProgrammingTime': 15066, 'qpuDelayTimePerSample': 20, 'postProcessingOverheadTime': 3454, 'totalPostProcessingTime': 29876, 'totalRealTime': None, 'runTimeChip': None, 'annealTimePerRun': None, 'readoutTimePerRun': None}
x_0 = 2
x_1 = 0
x_2 = 3
x_3 = 2
x_4 = 0
x_5 = 1
x_6 = 0
</code></pre>
<pre><code class="language-python">def solve_bqm_in_leap(bqm, sampler = &quot;Kerberos&quot;):
bqm.normalize()
if sampler == &quot;Kerberos&quot;:
kerberos_sampler = KerberosSampler().sample(bqm, max_iter=10, convergence=3, qpu_params={'label': 'Kakuro puzzle'})
sample = kerberos_sampler.first.sample
elif sampler == &quot;LeapHybrid&quot;:
sampler = LeapHybridSampler()
sampleset = sampler.sample(bqm)
sample = sampleset.first.sample
return sample
</code></pre>
<p>With D-wave&rsquo;s Leap, we obtain the correct result as well:</p>
<pre><code class="language-python">result = solve_bqm_in_leap(bqm, sampler = &quot;LeapHybrid&quot;)
for elem in result:
if result[elem] == 1:
print('x_' + str(elem[0]) + ' = ' + str(elem[1]))
</code></pre>
<p>If you don&rsquo;t have access to Amazon Braket or D-wave Leap, you can also use the local classical simulated annealing module. Unfortunately, this one is usually relatively slow before we get good results. For example, we had 10 000 num_reads on the quantum computer and 1000 on the local machine. But 1000 shots is not enough for the classical simulated annealing. The quality depends significantly on the parameters, which are sometimes challenging to find (or I have not yet learned).</p>
<pre><code class="language-python">def solve_with_simulated_annealing(bqm):
sampler = dimod.SimulatedAnnealingSampler()
sampleset = sampler.sample(bqm, num_reads=1000)
sample = sampleset.first.sample