Skip to content

Commit 5ac8f83

Browse files
committed
fix permutation gate synthesis
Perm gates were being synthesized as inverses, leading to improper compilations. Added a battery of new tests for checking multi-qubit perm gate synthesis. Fixes #805.
1 parent 4444207 commit 5ac8f83

File tree

3 files changed

+193
-107
lines changed

3 files changed

+193
-107
lines changed

src/compilers/permutation.lisp

+97-76
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
;;;; permutation.lisp
2+
;;;;
3+
;;;; Author: Charles Zhang
4+
5+
(in-package #:cl-quil)
6+
27
;;;; This file implements routines to decompose and synthesize
38
;;;; permutation matrices representing classical reversible circuits.
49

5-
(in-package #:cl-quil)
610

711
;;;; young group decomposition based algorithm for permutation gates
812

@@ -63,6 +67,7 @@
6367
(dotimes (i size)
6468
(assert (= (aref right (aref perm (aref left i)))
6569
(aref old-perm i))))
70+
;; Return g1 and g2 in "mathematical" or "composition" order.
6671
(values left right))))
6772

6873
(defstruct single-target-gate
@@ -73,74 +78,94 @@
7378
(target (missing-arg) :type fixnum :read-only t))
7479

7580
(defun single-target-gate-decomposition! (perm)
76-
"PERM permutes computational basis to computational basis. This algorithm produces at most 2n-1 single target gates where n is the number of bits, which is nearly optimal."
77-
(let ((length (length perm)))
78-
(assert (power-of-two-p length))
79-
(let ((n-qubits (1- (integer-length length)))
80-
(left-gates '())
81-
(right-gates '()))
82-
(dotimes (qubit n-qubits)
83-
(multiple-value-bind (left right)
84-
(decompose! perm qubit)
85-
(flet ((single-target-gate-from-permutation (permutation)
86-
(multiple-value-bind (truth-table vars)
87-
(truth-table-minimize-base!
88-
(make-truth-table n-qubits
89-
:initial-contents
90-
(loop for value across permutation
91-
for row from 0
92-
collect (if (= value row) 0 1))))
93-
(unless (truth-table-zero-p truth-table)
94-
(assert (not (member qubit vars)))
95-
(make-single-target-gate
96-
:function truth-table
97-
:control-lines (coerce (sort vars #'<) 'vector)
98-
:target qubit)))))
99-
(let ((left-gate (single-target-gate-from-permutation left))
100-
(right-gate (single-target-gate-from-permutation right)))
101-
(when left-gate (push left-gate left-gates))
102-
(when right-gate (push right-gate right-gates))))))
103-
;; Combine the left and right gates in the right order. It's
104-
;; possible as an additional optimization to merge the
105-
;; right-most left gate and the left-most right gate (assuming
106-
;; neither is the identity) because they act on the same
107-
;; target. In fact, probably any consecutive gates acting on the
108-
;; same target can be merged iteratively.
109-
(nreconc left-gates right-gates))))
81+
"PERM permutes computational basis to computational basis. This algorithm produces at most 2n-1 single target gates (acting on qubits numbered 0 to n-1) where n is the number of bits, which is nearly optimal."
82+
(assert (power-of-two-p (length perm)))
83+
(let ((n-qubits (ilog2 (length perm)))
84+
(left-gates '())
85+
(right-gates '()))
86+
(dotimes (index n-qubits)
87+
(multiple-value-bind (left right)
88+
(decompose! perm index)
89+
(flet ((single-target-gate-from-permutation (permutation)
90+
(multiple-value-bind (truth-table vars)
91+
(truth-table-minimize-base!
92+
(make-truth-table n-qubits
93+
:initial-contents
94+
(loop :for value :across permutation
95+
:for row :from 0
96+
:collect (if (= value row) 0 1))))
97+
(unless (truth-table-zero-p truth-table)
98+
(assert (not (member index vars)))
99+
(make-single-target-gate
100+
:function truth-table
101+
:control-lines (coerce (sort vars #'<) 'vector)
102+
:target index)))))
103+
(let ((left-gate (single-target-gate-from-permutation left))
104+
(right-gate (single-target-gate-from-permutation right)))
105+
(when left-gate (push left-gate left-gates))
106+
(when right-gate (push right-gate right-gates))))))
107+
;; Combine the left and right gates in "Quil" order (composition
108+
;; applies left-to-right), hence the unintuitive "right" listed
109+
;; before "left".
110+
;;
111+
;; It's possible as an additional optimization to merge the
112+
;; right-most left gate and the left-most right gate (assuming
113+
;; neither is the identity) because they act on the same
114+
;; target. In fact, probably any consecutive gates acting on the
115+
;; same target can be merged iteratively.
116+
(nreconc right-gates left-gates)))
110117

111118
;;; Synthesize a single target gate by using PPRM (positive polarity
112-
;;; Reed-Mueller form).
113-
(defun simple-single-target-gate-synthesize (gate)
114-
(let ((control-lines (single-target-gate-control-lines gate))
115-
(target (single-target-gate-target gate))
116-
(function (single-target-gate-function gate))
117-
(circuit '()))
118-
(let ((esop (truth-table-esop-from-pprm function)))
119-
(dolist (cube esop)
120-
(let ((translated-controls '()))
121-
(dotimes (index (length cube))
122-
(let ((trit (aref cube index)))
123-
(assert (/= trit -1)) ; *PP*RM
124-
(unless (= trit 0)
125-
(push (aref control-lines index) translated-controls))))
126-
(case (length translated-controls)
127-
(0 (push (build-gate "X" () target) circuit))
128-
(1 (push (build-gate "CNOT" () (first translated-controls) target) circuit))
129-
(2 (push (build-gate "CCNOT" ()
130-
(first translated-controls)
131-
(second translated-controls)
132-
target)
133-
circuit))
134-
(t
135-
(push (apply #'build-multiple-controlled-gate
136-
"X" () (append translated-controls (list target)))
137-
circuit))))))
138-
(nreverse circuit)))
139-
140-
(defun synthesize-permutation (permutation)
141-
(let ((permutation (make-array (length permutation) :initial-contents permutation)))
142-
(reduce #'append (mapcar #'simple-single-target-gate-synthesize
143-
(single-target-gate-decomposition! permutation)))))
119+
;;; Reed-Mueller) form.
120+
(defun simple-single-target-gate-synthesize (gate &optional (index->qubit #'identity))
121+
"Synthesize a single-target gate GATE, loosely representing the Quil instruction:
122+
123+
GATE k_0 k_1 ... k_(n-1)
124+
125+
where
126+
127+
k_i := INDEX->QUBIT(i)
128+
129+
By default, INDEX->QUBIT is the identity function, leading to a reverse convention of the standard Quil order of arguments."
130+
(let ((control-lines (map 'vector index->qubit (single-target-gate-control-lines gate)))
131+
(target (funcall index->qubit (single-target-gate-target gate)))
132+
(circuit '()))
133+
(dolist (cube (truth-table-esop-from-pprm (single-target-gate-function gate)))
134+
(let ((translated-controls '()))
135+
(dotimes (index (length cube))
136+
(let ((trit (aref cube index)))
137+
(assert (/= trit -1)) ; *PP*RM
138+
(unless (= trit 0)
139+
(push (aref control-lines index) translated-controls))))
140+
(case (length translated-controls)
141+
(0 (push (build-gate "X" () target) circuit))
142+
(1 (push (build-gate "CNOT" () (first translated-controls) target) circuit))
143+
(2 (push (build-gate "CCNOT" ()
144+
(first translated-controls)
145+
(second translated-controls)
146+
target)
147+
circuit))
148+
(t
149+
(push (apply #'build-multiple-controlled-gate
150+
"X" () (append translated-controls (list target)))
151+
circuit)))))
152+
;; Return the circuit in "Quil" order (left-to-right).
153+
circuit))
154+
155+
(defun synthesize-permutation (permutation qubits)
156+
"Synthesize the operator represented by the permutation PERMUTATION acting on the vector of qubits QUBITS.
157+
158+
If permutation represents a matrix mapping the i'th amplitude to the PERMUTATION[i]'th amplitude of a quantum state of qubit indexes {0, 1, ..., n-1}, then QUBITS must be the vector
159+
160+
#(n-1 n-2 ... 2 1 0)
161+
162+
in accordance with how Quil interprets a gate application."
163+
(let ((n (length qubits)))
164+
(flet ((index->qubit (i) (aref qubits (- n i 1))))
165+
(declare (dynamic-extent #'index->qubit))
166+
(loop :with permutation := (make-array (length permutation) :initial-contents permutation)
167+
:for target-gate :in (single-target-gate-decomposition! permutation)
168+
:nconcing (simple-single-target-gate-synthesize target-gate #'index->qubit)))))
144169

145170
(defun permutation-gate-to-mcx (instr &key context)
146171
"Compile instructions representing permutation gates to n-qubit Toffoli gates."
@@ -153,17 +178,13 @@
153178
(let* ((perm-gate (funcall
154179
(operator-description-gate-lifter
155180
(application-operator instr))
156-
res))
157-
(qubits (reverse (application-arguments instr))))
181+
res)))
158182
(cond
159183
((and (typep perm-gate 'permutation-gate)
160-
(> (length qubits) 2))
161-
(let* ((perm (permutation-gate-permutation perm-gate))
162-
(code (synthesize-permutation perm))
163-
(relabler (lambda (q)
164-
(setf (qubit-index q)
165-
(qubit-index (nth (qubit-index q) qubits))))))
166-
(map nil (a:rcurry #'cl-quil.frontend::%relabel-qubits relabler) code)
184+
(> (gate-dimension perm-gate) 4)) ; N.B. Dimension, not arity!
185+
(let* ((code (synthesize-permutation
186+
(permutation-gate-permutation perm-gate)
187+
(map 'vector #'qubit-index (application-arguments instr)))))
167188
;; If synthesis produces a 1 instruction sequence, that
168189
;; means that the original instruction represents a n-qubit
169190
;; controlled Toffoli gate, so we didn't do anything and

src/compilers/truth-table.lisp

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
;;;; truth-table.lisp
2-
;;;; This file contains the data structure representation for
3-
;;;; manipulating boolean functions.
2+
;;;;
3+
;;;; Author: Charles Zhang
44

55
(in-package #:cl-quil)
66

7+
;;;; This file contains the data structure representation for
8+
;;;; manipulating boolean functions.
9+
710
(defun missing-arg ()
811
(error "Required argument missing."))
912

@@ -73,8 +76,8 @@
7376
(truth-table-n-vars truth-table)
7477
:initial-contents
7578
(let ((bits (truth-table-bits truth-table)))
76-
(loop for k from 0 below (length bits)
77-
collect (aref bits (logandc2 k (ash 1 index)))))))
79+
(loop :for k :from 0 :below (length bits)
80+
:collect (aref bits (logandc2 k (ash 1 index)))))))
7881

7982
(defun truth-table-cofactor1 (truth-table index)
8083
"Find the 1-cofactor of the TRUTH-TABLE with respect to INDEX."
@@ -83,8 +86,8 @@
8386
(truth-table-n-vars truth-table)
8487
:initial-contents
8588
(let ((bits (truth-table-bits truth-table)))
86-
(loop for k from 0 below (length bits)
87-
collect (aref bits (logior k (ash 1 index)))))))
89+
(loop :for k :from 0 :below (length bits)
90+
:collect (aref bits (logior k (ash 1 index)))))))
8891

8992
(defun truth-table-xor (truth-table1 truth-table2)
9093
(declare (type truth-table truth-table1 truth-table2))
@@ -103,16 +106,16 @@
103106
(declare (type (member -1 1) polarity))
104107
(let* ((new-length (max (length cube) (1+ index)))
105108
(new-cube (make-array new-length :initial-element 0)))
106-
(loop for i from 0
107-
for trit across cube
108-
do (setf (aref new-cube i) trit))
109+
(loop :for i :from 0
110+
:for trit :across cube
111+
:do (setf (aref new-cube i) trit))
109112
(setf (aref new-cube index) polarity)
110113
new-cube))
111114

112-
;;; This algorithm applies recursively the positive Davio decomposition
113-
;;; which eventually leads into the PPRM representation of a
114-
;;; function. An ESOP (Exclusive Sum Of Products) is represented by a
115-
;;; list of cubes.
115+
;;; This algorithm applies recursively the positive Davio
116+
;;; decomposition which eventually leads into the PPRM representation
117+
;;; of a function. An ESOP (Exclusive Sum Of Products) is represented
118+
;;; by a list of cubes.
116119
(defun truth-table-esop-from-pprm (truth-table)
117120
"Given a truth table, return a list of cubes which when disjoined represent the PPRM representation of the boolean function encoded by TRUTH-TABLE."
118121
(let ((cubes (make-hash-table :test #'equalp)))

tests/permutation-tests.lisp

+80-18
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,90 @@
1111
(matrix (magicl:zeros (list size size) :type '(complex double-float))))
1212
(loop :for i :from 0
1313
:for j :across permutation
14-
:do (setf (magicl:tref matrix j i) 1))
14+
:do (setf (magicl:tref matrix i j) 1))
1515
matrix))
1616

17+
(defun qubits-in-computational-order (n)
18+
(nreverse (coerce (a:iota n) 'vector)))
19+
1720
(defun permutation-synthesis-as-parsed-program (permutation)
1821
(make-instance 'quil::parsed-program
19-
:executable-code (coerce (quil::synthesize-permutation permutation) 'vector)))
22+
:executable-code (coerce (quil::synthesize-permutation
23+
permutation
24+
(qubits-in-computational-order (cl-quil.frontend::ilog2 (length permutation))))
25+
'vector)))
2026

2127
;;; Test that the synthesized permutation when simulated performs the
2228
;;; action of the permutation.
23-
(deftest test-permutation-gates-logical-matrix-equivalent ()
24-
(flet ((test (permutation)
25-
(is (quil::operator=
26-
(quil:parsed-program-to-logical-matrix
27-
(permutation-synthesis-as-parsed-program permutation)
28-
:compress-qubits nil)
29-
(matrix-from-permutation permutation)))))
30-
(test #(0 1))
31-
(test #(1 0))
32-
(test #(3 0 1 2))
33-
(test #(2 0 3 1))
34-
(test #(1 0 3 2))
35-
(test #(0 2 3 1))
36-
(test #(2 1 3 0))
37-
(test #(3 1 2 0))
38-
(test +prime+)))
29+
(defun synthesize-and-check-permutation (permutation)
30+
(quil::operator=
31+
(quil:parsed-program-to-logical-matrix
32+
(permutation-synthesis-as-parsed-program permutation)
33+
:compress-qubits nil)
34+
(matrix-from-permutation permutation)))
35+
36+
(deftest test-permutation-gates-logical-matrix-equivalent-examples ()
37+
(let ((perms (list #(0 1)
38+
#(1 0)
39+
#(3 0 1 2)
40+
#(2 0 3 1)
41+
#(1 0 3 2)
42+
#(0 2 3 1)
43+
#(2 1 3 0)
44+
#(3 1 2 0)
45+
+prime+)))
46+
(dolist (p perms)
47+
(is (synthesize-and-check-permutation p)))))
48+
49+
(defun cl-perm-to-vec (p)
50+
(map 'vector #'1- (cl-permutation:perm-to-vector p)))
51+
52+
(deftest test-permutation-gates-logical-matrix-equivalent-big ()
53+
(loop :for i :from 1 :to 3
54+
:for n := (expt 2 i)
55+
:for start := (get-internal-real-time)
56+
:do (format t "~&Testing permutations of dimension ~D..." n)
57+
(cl-permutation:doperms (p n)
58+
(is (synthesize-and-check-permutation (cl-perm-to-vec p))))
59+
(format t " done [~D ms]~%" (round (* 1000 (- (get-internal-real-time) start))
60+
internal-time-units-per-second))))
61+
62+
(deftest test-perm-compilation-gh805 ()
63+
(let* ((chip (quil::build-nq-linear-chip 3 :architecture ':cnot))
64+
(orig-prog (quil::parse-quil "
65+
DEFGATE PERM AS PERMUTATION:
66+
5, 1, 2, 6, 7, 0, 4, 3
67+
68+
X 0
69+
PERM 0 1 2
70+
"))
71+
(orig-matrix (quil:parsed-program-to-logical-matrix orig-prog))
72+
(proc-prog (quil::compiler-hook orig-prog chip))
73+
(proc-matrix (quil:parsed-program-to-logical-matrix proc-prog))
74+
(2q-code (program-2q-instructions proc-prog)))
75+
(is (quil::matrix-equals-dwim orig-matrix proc-matrix))
76+
(is (every (link-nativep chip) 2q-code))))
77+
78+
(deftest test-random-3q-perm-compilations ()
79+
(flet ((perm->prog (perm)
80+
(format nil "
81+
DEFGATE PERM AS PERMUTATION:
82+
~{~D~^, ~}
83+
84+
X 0
85+
PERM 0 1 2
86+
"
87+
perm)))
88+
(let ((chip (quil::build-nq-linear-chip 3 :architecture ':cnot))
89+
(*print-pretty* nil))
90+
(loop :repeat 16 :do
91+
(let* ((perm (alexandria:shuffle (alexandria:iota 8)))
92+
(orig-prog (quil::parse-quil (perm->prog perm)))
93+
(orig-matrix (quil:parsed-program-to-logical-matrix orig-prog))
94+
(proc-prog (quil::compiler-hook orig-prog chip))
95+
(proc-matrix (quil:parsed-program-to-logical-matrix proc-prog))
96+
(2q-code (program-2q-instructions proc-prog)))
97+
(format t " Testing compiling perm ~A...~%" perm)
98+
(is (quil::matrix-equals-dwim orig-matrix proc-matrix))
99+
(is (every (link-nativep chip) 2q-code)))))))
100+

0 commit comments

Comments
 (0)