From 7a6339d1a74beec0f46c84d58e540cf47216a271 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 27 Sep 2019 21:57:12 -0400 Subject: [PATCH 01/35] checkpoint: parsing and printing support for AS PAULI-SUM --- src/ast.lisp | 31 +++++++++++- src/parser.lisp | 127 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/ast.lisp b/src/ast.lisp index e6cb47728..7a9ac5003 100644 --- a/src/ast.lisp +++ b/src/ast.lisp @@ -386,6 +386,20 @@ If no exit rewiring is found, return NIL." :reader permutation-gate-definition-permutation)) (:documentation "A gate definition whose entries can be represented by a permutation of natural numbers.")) +(defclass pauli-sum-gate-definition (gate-definition) + ((terms :initarg :terms + :reader pauli-sum-gate-definition-terms + :documentation "") ; XXX + (parameters :initarg :parameters + :reader pauli-sum-gate-definition-parameters + :documentation "") ; XXX + (arguments :initarg :arguments + :reader pauli-sum-gate-definition-arguments + :documentation ""))) ; XXX + +(defmethod gate-definition-qubits-needed ((gate pauli-sum-gate-definition)) + (length (pauli-sum-gate-definition-arguments gate))) + (defmethod gate-definition-qubits-needed ((gate permutation-gate-definition)) (ilog2 (length (permutation-gate-definition-permutation gate)))) @@ -1528,7 +1542,22 @@ For example, (format stream ":~%") (print-instruction-sequence (circuit-definition-body defn) :stream stream - :prefix " "))) + :prefix " ")) + + (:method ((gate pauli-sum-gate-definition) (stream stream)) + (format stream "DEFGATE ~A~@[(~{%~A~^, ~})~]~{ ~A~} AS PAULI-SUM:~%" + (gate-definition-name gate) + (mapcar #'param-name (pauli-sum-gate-definition-parameters gate)) + (mapcar #'formal-name (pauli-sum-gate-definition-arguments gate))) + (dolist (pauli-term (pauli-sum-gate-definition-terms gate)) + (with-slots (pauli-word prefactor arguments) pauli-term + (format stream " ~a(" pauli-word) + (print-instruction prefactor stream) + (format stream ")") + (dolist (arg arguments) + (format stream " ") + (print-instruction arg stream)) + (terpri stream))))) (defmethod print-object ((object instruction) stream) (print-unreadable-object (object stream :type nil :identity nil) diff --git a/src/parser.lisp b/src/parser.lisp index 4278d44ed..6cfcb0756 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -38,7 +38,7 @@ :LOAD :STORE :EQ :GT :GE :LT :LE :DEFGATE :DEFCIRCUIT :RESET :HALT :WAIT :LABEL :NOP :CONTROLLED :DAGGER :FORKED :DECLARE :SHARING :OFFSET :PRAGMA - :AS :MATRIX :PERMUTATION)) + :AS :MATRIX :PERMUTATION :PAULI-SUM)) (deftype token-type () '(or @@ -148,7 +148,7 @@ Each lexer extension is a function mapping strings to tokens. They are used to h (return (tok ':CONTROLLED))) ((eager #.(string #\OCR_FORK)) (return (tok ':FORKED))) - ("INCLUDE|DEFCIRCUIT|DEFGATE|MEASURE|LABEL|WAIT|NOP|HALT|RESET|JUMP\\-WHEN|JUMP\\-UNLESS|JUMP|PRAGMA|NOT|AND|IOR|MOVE|EXCHANGE|SHARING|DECLARE|OFFSET|XOR|NEG|LOAD|STORE|CONVERT|ADD|SUB|MUL|DIV|EQ|GT|GE|LT|LE|CONTROLLED|DAGGER|FORKED|AS|MATRIX|PERMUTATION" + ("INCLUDE|DEFCIRCUIT|DEFGATE|MEASURE|LABEL|WAIT|NOP|HALT|RESET|JUMP\\-WHEN|JUMP\\-UNLESS|JUMP|PRAGMA|NOT|AND|IOR|MOVE|EXCHANGE|SHARING|DECLARE|OFFSET|XOR|NEG|LOAD|STORE|CONVERT|ADD|SUB|MUL|DIV|EQ|GT|GE|LT|LE|CONTROLLED|DAGGER|FORKED|AS|MATRIX|PERMUTATION|PAULI-SUM" (return (tok (intern $@ :keyword)))) ((eager "(?{{IDENT}})\\[(?{{INT}})\\]") (assert (not (null $NAME))) @@ -844,6 +844,7 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (defun parse-gate-definition (tok-lines) "Parse a gate definition from the token lines TOK-LINES." + (declare (optimize (debug 3) (speed 0) (space 0))) ;; Check that we have tokens left (when (null tok-lines) (quil-parse-error "EOF reached when gate definition expected")) @@ -870,21 +871,119 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (setf name (token-payload (pop params-args))) (multiple-value-bind (params rest-line) (parse-parameters params-args) + (multiple-value-bind (args rest-line) (parse-arguments rest-line) (when (eql ':AS (token-type (first rest-line))) (pop rest-line) (let* ((parsed-gate-tok (first rest-line)) (parsed-gate-type (token-type parsed-gate-tok))) - (unless (find parsed-gate-type '(:MATRIX :PERMUTATION)) + (unless (find parsed-gate-type '(:MATRIX :PERMUTATION :PAULI-SUM)) (quil-parse-error "Found unexpected gate type: ~A." (token-payload parsed-gate-tok))) (setf gate-type parsed-gate-type))) (ecase gate-type (:MATRIX + (when args + (quil-parse-error "DEFGATE AS MATRIX cannot carry formal qubit arguments.")) (parse-gate-entries-as-matrix body-lines params name :lexical-context op)) (:PERMUTATION + (when args + (quil-parse-error "DEFGATE AS PERMUTATION cannot carry formal qubit arguments.")) (when params (quil-parse-error "Permutation gate definitions do not support parameters.")) - (parse-gate-entries-as-permutation body-lines name :lexical-context op)))))))) + (parse-gate-entries-as-permutation body-lines name :lexical-context op)) + (:PAULI-SUM + (parse-gate-definition-body-into-pauli-sum body-lines name + :lexical-context op + :legal-arguments args + :legal-parameters params))))))))) + +(defun parse-gate-definition-body-into-pauli-sum (body-lines name &key lexical-context legal-arguments legal-parameters) + ;; is the immediate next line indented? if not, error. + (multiple-value-bind (indented? modified-line) + (indented-line (first body-lines)) + (unless indented? + (quil-parse-error "Declaration DEFGATE ~a ... AS PAULI-SUM has empty body." + name)) + ;; strip off the indentation. + (setf body-lines (list* modified-line (rest body-lines))) + ;; otherwise, iterate til we hit a dedent token. + (loop :for line :in body-lines + :for rest-lines := (rest body-lines) :then (rest rest-lines) + :with parsed-entries := nil + :do (multiple-value-bind (dedented? modified-line) + (dedented-line-p line) + ;; if we're done, return the gate definition (and the rest of the lines) + (when dedented? + (return-from parse-gate-definition-body-into-pauli-sum + (values (make-instance 'pauli-sum-gate-definition + :name name + :terms (nreverse parsed-entries) + :context lexical-context + :arguments legal-arguments + :parameters legal-parameters) + (list* modified-line rest-lines)))) + ;; store this word/qubits pair as part of the gate definition + (let ((app (parse-application (list line)))) + (unless (= 1 (length (application-parameters app))) + (quil-parse-error "Pauli term requires a parenthesized scalar factor, but found ~a of them." + (length (application-parameters app)))) + (setf parsed-entries (list* (make-pauli-term :pauli-word (adt:with-data (named-operator name) (application-operator app) name) + :prefactor (first (application-parameters app)) + :arguments (application-arguments app)) + parsed-entries))))))) + +(defstruct (pauli-term) + pauli-word + prefactor + arguments) + +(defun parse-pauli-sum-line (line &key lexical-context legal-arguments legal-parameters) + "Parses a line inside of a DEFGATE ... AS PAULI-SUM body." + (declare (ignore lexical-context)) + (let ((*arithmetic-parameters* nil) + pauli-word param qubit-list) + (when (null line) + (quil-parse-error "Empty line found in DEFGATE AS PAULI-SUM body.")) + ;; PAULI-WORD + (unless (eql ':NAME (token-type (first line))) + (quil-parse-error "DEFGATE AS PAULI-SUM body line begins with something other than a Pauli word: ~a" (first line))) + (setf pauli-word (token-payload (pop line))) + ;; LPAREN + (unless (eql ':LEFT-PAREN (token-type (first line))) + (quil-parse-error "Pauli term requires a parenthesized scalar factor, but found ~a instead of LPAREN" (first line))) + (pop line) + ;; PARAMETER + (multiple-value-bind (param-tokens line) + (take-until (lambda (x) + (pop line) + (eql ':RIGHT-PAREN (token-type x))) + line) + (when (null line) + (quil-parse-error "Pauli term has unmatched left-parenthesis.")) + (setf param (parse-arithmetic-tokens param-tokens :eval t)) + ;; RPAREN + (unless (eql ':RIGHT-PAREN (token-type (first line))) + ) + (pop line) + ;; QUBIT ... QUBIT + (setf qubit-list (mapcar (lambda (tok) + (unless (eql ':NAME (token-type tok)) + (quil-parse-error "Expected a formal qubit argument, but got ~a" tok)) + (let ((ret (parse-argument tok))) + (unless (member ret legal-arguments :test #'equalp) + (quil-parse-error "Found formal qubit argument ~a not present in the argument list.")) + ret)) + line)) + ;; some further Sanity Chex: + ;; there are as many paulis as qubits + (unless (= (length qubit-list) (length pauli-word)) + (quil-parse-error "Pauli word ~a expected ~a qubit arguments, but got ~a: ~a" + pauli-word (length pauli-word) (length qubit-list) qubit-list)) + ;; the parameter body only refers to defined terms + ;; XXX + (make-pauli-term :pauli-word pauli-word + :prefactor param + :arguments qubit-list)))) (defun parse-gate-entries-as-permutation (body-lines name &key lexical-context) (multiple-value-bind (parsed-entries rest-lines) @@ -1411,6 +1510,26 @@ When ALLOW-EXPRESSIONS is set, we allow for general arithmetic expressions in a (values (mapcar parse-op entries) rest-line)))) +(defun parse-arguments (params-args) + ;; Parse out until we hit :AS or :COLON. + (multiple-value-bind (found-args rest-line) + (take-until (lambda (x) + (or (eql ':AS (token-type x)) + (eql ':COLON (token-type x)) + ;; do this pop last + (not (pop params-args)))) + params-args) + ;; Make sure we have tokens left over: the line can't end in this state. + (when (null rest-line) + (quil-parse-error "Unterminated argument list in DEFGATE.")) + + ;; All the intermediate tokens in found-args should be formal qubit names + (setf args (loop :for a :in found-args + :unless (eql ':NAME (token-type a)) + :do (quil-parse-error "Found something other than a formal qubit name in a DEFGATE argument list: ~A" a) + :collect (parse-argument a))) + (values args rest-line))) + ;;;;;;;;;;;;;;;;;;;;;;;;; Arithmetic Parser ;;;;;;;;;;;;;;;;;;;;;;;;;; (defun token-generator (toks) From 0f3e49359fecede32a61d752a98a67423583a9e7 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 10:26:05 -0400 Subject: [PATCH 02/35] dorky matrix exponential --- src/matrix-operations.lisp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/matrix-operations.lisp b/src/matrix-operations.lisp index 7a9fad124..2bed4326b 100644 --- a/src/matrix-operations.lisp +++ b/src/matrix-operations.lisp @@ -391,3 +391,11 @@ as needed so that they are the same size." (matrix-equality kroned-ref-mat (scale-out-matrix-phases kroned-mat kroned-ref-mat)))))) + +(defun matrix-expt (m s) + "Computes EXP(M*S). Only works for unitarily diagonalizable matrices M." + (multiple-value-bind (d u) (magicl:eig m) + (let* ((size (length d)) + (dd (magicl:diag size size + (mapcar (lambda (z) (exp (* z s))) d)))) + (m* u dd (magicl:conjugate-transpose u))))) From c08786ba5b7a16e987730db7096e2d0e66e84060 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 10:26:41 -0400 Subject: [PATCH 03/35] misguided? first attempt at gates from pauli-sum definitions --- src/gates.lisp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/gates.lisp b/src/gates.lisp index f33d56fb7..fb55884b2 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -189,6 +189,17 @@ (defmethod gate-matrix ((gate parameterized-gate) &rest parameters) (apply (parameterized-gate-matrix-function gate) parameters)) +(defclass pauli-sum-gate (parameterized-gate) + ((dimension :initarg :dimension + :reader gate-dimension) + (parameters :initarg :parameters + :reader pauli-sum-gate-parameters) + (arguments :initarg :arguments + :reader pauli-sum-gate-arguments) + (terms :initarg :terms + :reader pauli-sum-gate-terms)) + (:documentation "A gate specified by a Pauli sum.")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;; Gate Operators ;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Controlled Gates @@ -357,6 +368,42 @@ :arity (length params) :matrix-function (compile nil (lambda-form params dim entries)))))) +(defmethod gate-definition-to-gate ((gate-def pauli-sum-gate-definition)) + (declare (optimize (debug 3) (speed 0))) + (with-slots (arguments parameters terms) gate-def + (let ((size (expt 2 (length (pauli-sum-gate-definition-arguments gate-def))))) + (flet ((matrix-function (&optional params) + (let ((m (magicl:make-zero-matrix size size))) + (dolist (term terms) + (dotimes (col size) + (let ((row col) + ;; XXX: this needs to be evaluated against PARAMS + (entry (constant-value (pauli-term-prefactor term)))) + (loop :for letter :across (pauli-term-pauli-word term) + :for arg :in (pauli-term-arguments term) + :for arg-position := (position arg arguments :test #'equalp) + :for row-toggle := (ldb (byte 1 arg-position) col) + :do (ecase letter + (#\X + (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) + (setf entry (- entry))) + (#\Y + (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) + (setf entry (* entry (if (zerop row-toggle) (complex 0 1) (complex 0 -1))))) + (#\Z + (setf entry (* entry (if (zerop row-toggle) 1 -1)))) + (#\I + nil))) + (incf (magicl:ref m row col) entry)))) + (matrix-expt m (complex 0d0 -1d0))))) + (make-instance 'pauli-sum-gate + :arguments (pauli-sum-gate-definition-arguments gate-def) + :parameters (pauli-sum-gate-definition-parameters gate-def) + :terms (pauli-sum-gate-definition-terms gate-def) + :dimension size + :arity (length (pauli-sum-gate-definition-arguments gate-def)) + :matrix-function #'matrix-function))))) + ;;;; some leftover stuff from standard-gates.lisp and elsewhere (defun apply-gate (m instr) From f495350862e5f8657e8fe3f361301d33eb69d0c7 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 12:46:21 -0400 Subject: [PATCH 04/35] add missing error --- src/parser.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.lisp b/src/parser.lisp index 6cfcb0756..ebaba43af 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -963,7 +963,7 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (setf param (parse-arithmetic-tokens param-tokens :eval t)) ;; RPAREN (unless (eql ':RIGHT-PAREN (token-type (first line))) - ) + (quil-parse-error "Expected a right parenthesis in parsing this Pauli term, but got ~a" (first line))) (pop line) ;; QUBIT ... QUBIT (setf qubit-list (mapcar (lambda (tok) From 6339517bee913426fd297e4936f4c5c01215d986 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 12:46:39 -0400 Subject: [PATCH 05/35] move pauli-term->matrix nearer to definition of pauli-term --- src/gates.lisp | 29 ++++++----------------------- src/parser.lisp | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/gates.lisp b/src/gates.lisp index fb55884b2..826d748e7 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -373,29 +373,12 @@ (with-slots (arguments parameters terms) gate-def (let ((size (expt 2 (length (pauli-sum-gate-definition-arguments gate-def))))) (flet ((matrix-function (&optional params) - (let ((m (magicl:make-zero-matrix size size))) - (dolist (term terms) - (dotimes (col size) - (let ((row col) - ;; XXX: this needs to be evaluated against PARAMS - (entry (constant-value (pauli-term-prefactor term)))) - (loop :for letter :across (pauli-term-pauli-word term) - :for arg :in (pauli-term-arguments term) - :for arg-position := (position arg arguments :test #'equalp) - :for row-toggle := (ldb (byte 1 arg-position) col) - :do (ecase letter - (#\X - (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) - (setf entry (- entry))) - (#\Y - (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) - (setf entry (* entry (if (zerop row-toggle) (complex 0 1) (complex 0 -1))))) - (#\Z - (setf entry (* entry (if (zerop row-toggle) 1 -1)))) - (#\I - nil))) - (incf (magicl:ref m row col) entry)))) - (matrix-expt m (complex 0d0 -1d0))))) + (assert (= (length parameters) (length params))) + (matrix-expt (reduce (lambda (m term) + (m+ m (pauli-term->matrix term arguments params))) + terms + :initial-value (magicl:make-zero-matrix size size)) + (complex 0d0 -1d0)))) (make-instance 'pauli-sum-gate :arguments (pauli-sum-gate-definition-arguments gate-def) :parameters (pauli-sum-gate-definition-parameters gate-def) diff --git a/src/parser.lisp b/src/parser.lisp index ebaba43af..359a2bf98 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -985,6 +985,31 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an :prefactor param :arguments qubit-list)))) +(defun pauli-term->matrix (term arguments parameters) + (let* ((size (expt 2 (length arguments))) + (m (magicl:make-zero-matrix size size))) + (dotimes (col size) + (let ((row col) + ;; XXX: this needs to be evaluated against PARAMS + (entry (constant-value (pauli-term-prefactor term)))) + (loop :for letter :across (pauli-term-pauli-word term) + :for arg :in (pauli-term-arguments term) + :for arg-position := (position arg arguments :test #'equalp) + :for row-toggle := (ldb (byte 1 arg-position) col) + :do (ecase letter + (#\X + (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) + (setf entry (- entry))) + (#\Y + (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) + (setf entry (* entry (if (zerop row-toggle) (complex 0 1) (complex 0 -1))))) + (#\Z + (setf entry (* entry (if (zerop row-toggle) 1 -1)))) + (#\I + nil))) + (incf (magicl:ref m row col) entry))) + m)) + (defun parse-gate-entries-as-permutation (body-lines name &key lexical-context) (multiple-value-bind (parsed-entries rest-lines) (parse-permutation-gate-entries body-lines) From b1845750c9dd4624b74bcd9841f98b25d4618a26 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 16:26:08 -0400 Subject: [PATCH 06/35] track symbols correctly for parameter functions --- src/ast.lisp | 8 +++-- src/gates.lisp | 4 +-- src/parser.lisp | 82 ++++++++++++++++++++++++++----------------------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/ast.lisp b/src/ast.lisp index 7a9ac5003..9eebeae15 100644 --- a/src/ast.lisp +++ b/src/ast.lisp @@ -1547,12 +1547,16 @@ For example, (:method ((gate pauli-sum-gate-definition) (stream stream)) (format stream "DEFGATE ~A~@[(~{%~A~^, ~})~]~{ ~A~} AS PAULI-SUM:~%" (gate-definition-name gate) - (mapcar #'param-name (pauli-sum-gate-definition-parameters gate)) + (mapcar #'string (pauli-sum-gate-definition-parameters gate)) (mapcar #'formal-name (pauli-sum-gate-definition-arguments gate))) (dolist (pauli-term (pauli-sum-gate-definition-terms gate)) (with-slots (pauli-word prefactor arguments) pauli-term (format stream " ~a(" pauli-word) - (print-instruction prefactor stream) + (typecase prefactor + (number + (format stream "~a" prefactor)) + (cons + (print-instruction (make-delayed-expression nil nil prefactor) stream))) (format stream ")") (dolist (arg arguments) (format stream " ") diff --git a/src/gates.lisp b/src/gates.lisp index 826d748e7..e7a6a17c0 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -372,10 +372,10 @@ (declare (optimize (debug 3) (speed 0))) (with-slots (arguments parameters terms) gate-def (let ((size (expt 2 (length (pauli-sum-gate-definition-arguments gate-def))))) - (flet ((matrix-function (&optional params) + (flet ((matrix-function (&rest params) (assert (= (length parameters) (length params))) (matrix-expt (reduce (lambda (m term) - (m+ m (pauli-term->matrix term arguments params))) + (m+ m (pauli-term->matrix term arguments params parameters))) terms :initial-value (magicl:make-zero-matrix size size)) (complex 0d0 -1d0)))) diff --git a/src/parser.lisp b/src/parser.lisp index 359a2bf98..0431d070f 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -899,37 +899,39 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (defun parse-gate-definition-body-into-pauli-sum (body-lines name &key lexical-context legal-arguments legal-parameters) ;; is the immediate next line indented? if not, error. - (multiple-value-bind (indented? modified-line) - (indented-line (first body-lines)) - (unless indented? - (quil-parse-error "Declaration DEFGATE ~a ... AS PAULI-SUM has empty body." - name)) - ;; strip off the indentation. - (setf body-lines (list* modified-line (rest body-lines))) - ;; otherwise, iterate til we hit a dedent token. - (loop :for line :in body-lines - :for rest-lines := (rest body-lines) :then (rest rest-lines) - :with parsed-entries := nil - :do (multiple-value-bind (dedented? modified-line) - (dedented-line-p line) - ;; if we're done, return the gate definition (and the rest of the lines) - (when dedented? - (return-from parse-gate-definition-body-into-pauli-sum - (values (make-instance 'pauli-sum-gate-definition - :name name - :terms (nreverse parsed-entries) - :context lexical-context - :arguments legal-arguments - :parameters legal-parameters) - (list* modified-line rest-lines)))) - ;; store this word/qubits pair as part of the gate definition - (let ((app (parse-application (list line)))) - (unless (= 1 (length (application-parameters app))) - (quil-parse-error "Pauli term requires a parenthesized scalar factor, but found ~a of them." - (length (application-parameters app)))) - (setf parsed-entries (list* (make-pauli-term :pauli-word (adt:with-data (named-operator name) (application-operator app) name) - :prefactor (first (application-parameters app)) - :arguments (application-arguments app)) + (let ((*segment-encountered* nil) + (*arithmetic-parameters* nil)) + (multiple-value-bind (indented? modified-line) + (indented-line (first body-lines)) + (unless indented? + (quil-parse-error "Declaration DEFGATE ~a ... AS PAULI-SUM has empty body." + name)) + ;; strip off the indentation. + (setf body-lines (list* modified-line (rest body-lines))) + ;; otherwise, iterate til we hit a dedent token. + (loop :for line :in body-lines + :for rest-lines := (rest body-lines) :then (rest rest-lines) + :with parsed-entries := nil + :do (multiple-value-bind (dedented? modified-line) + (dedented-line-p line) + ;; if we're done, return the gate definition (and the rest of the lines) + (when dedented? + (return-from parse-gate-definition-body-into-pauli-sum + (values (make-instance 'pauli-sum-gate-definition + :name name + :terms (nreverse parsed-entries) + :context lexical-context + :arguments legal-arguments + :parameters (mapcar (lambda (p) + (or (cadr (assoc p *arithmetic-parameters* :test #'equalp)) + (make-symbol (format nil "~a-UNUSED" (param-name p))))) + legal-parameters)) + (list* modified-line rest-lines)))) + ;; store this word/qubits pair as part of the gate definition + (setf parsed-entries (list* (parse-pauli-sum-line line + :lexical-context lexical-context + :legal-parameters legal-parameters + :legal-arguments legal-arguments) parsed-entries))))))) (defstruct (pauli-term) @@ -939,9 +941,8 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (defun parse-pauli-sum-line (line &key lexical-context legal-arguments legal-parameters) "Parses a line inside of a DEFGATE ... AS PAULI-SUM body." - (declare (ignore lexical-context)) - (let ((*arithmetic-parameters* nil) - pauli-word param qubit-list) + (declare (ignore lexical-context legal-parameters)) + (let (pauli-word param qubit-list) (when (null line) (quil-parse-error "Empty line found in DEFGATE AS PAULI-SUM body.")) ;; PAULI-WORD @@ -985,13 +986,18 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an :prefactor param :arguments qubit-list)))) -(defun pauli-term->matrix (term arguments parameters) - (let* ((size (expt 2 (length arguments))) +(defun pauli-term->matrix (term arguments parameters parameter-names) + (let* ((prefactor-fn + (typecase (pauli-term-prefactor term) + (number (lambda (&rest args) (declare (ignore args)) (pauli-term-prefactor term))) + (cons (compile nil + (print `(lambda ,parameter-names + ,@(pauli-term-prefactor term))))))) + (size (expt 2 (length arguments))) (m (magicl:make-zero-matrix size size))) (dotimes (col size) (let ((row col) - ;; XXX: this needs to be evaluated against PARAMS - (entry (constant-value (pauli-term-prefactor term)))) + (entry (apply prefactor-fn parameters))) (loop :for letter :across (pauli-term-pauli-word term) :for arg :in (pauli-term-arguments term) :for arg-position := (position arg arguments :test #'equalp) From cc5c4e8bf705aaed33dc13f4048a7c17d93050fd Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 Sep 2019 16:36:17 -0400 Subject: [PATCH 07/35] fix rotation error ; fix bare variable error --- src/parser.lisp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/parser.lisp b/src/parser.lisp index 0431d070f..1e055c8a9 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -990,17 +990,21 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (let* ((prefactor-fn (typecase (pauli-term-prefactor term) (number (lambda (&rest args) (declare (ignore args)) (pauli-term-prefactor term))) - (cons (compile nil - (print `(lambda ,parameter-names - ,@(pauli-term-prefactor term))))))) - (size (expt 2 (length arguments))) + (symbol (compile nil `(lambda ,parameter-names + (declare (ignorable ,@parameter-names)) + ,(pauli-term-prefactor term)))) + (cons (compile nil `(lambda ,parameter-names + (declare (ignorable ,@parameter-names)) + ,@(pauli-term-prefactor term)))))) + (arg-count (length arguments)) + (size (expt 2 arg-count)) (m (magicl:make-zero-matrix size size))) (dotimes (col size) (let ((row col) (entry (apply prefactor-fn parameters))) (loop :for letter :across (pauli-term-pauli-word term) :for arg :in (pauli-term-arguments term) - :for arg-position := (position arg arguments :test #'equalp) + :for arg-position := (- arg-count 1 (position arg arguments :test #'equalp)) :for row-toggle := (ldb (byte 1 arg-position) col) :do (ecase letter (#\X From f8e2df13777e9fe34be00f2204271596ec244771 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Oct 2019 08:38:20 -0400 Subject: [PATCH 08/35] kill stray debug line --- src/gates.lisp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gates.lisp b/src/gates.lisp index e7a6a17c0..3c04f45ce 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -369,7 +369,6 @@ :matrix-function (compile nil (lambda-form params dim entries)))))) (defmethod gate-definition-to-gate ((gate-def pauli-sum-gate-definition)) - (declare (optimize (debug 3) (speed 0))) (with-slots (arguments parameters terms) gate-def (let ((size (expt 2 (length (pauli-sum-gate-definition-arguments gate-def))))) (flet ((matrix-function (&rest params) From 0e43aae112c725301656bdb44c803dfaaca14882 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Oct 2019 19:11:20 -0400 Subject: [PATCH 09/35] attic optimal-2q.lisp --- cl-quil.asd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl-quil.asd b/cl-quil.asd index 75205657f..3502624e3 100644 --- a/cl-quil.asd +++ b/cl-quil.asd @@ -80,8 +80,8 @@ (:file "state-prep") (:file "translators") (:file "modifiers") + ;; attic'd files / pedagogical purposes only (:file "optimal-2q") - ;; attic'd file / pedagogical purposes only (:static-file "cs-compile"))) (:module "analysis" :serial t From ebd1f13b502dbb1935136ba1a6bc665b577b78de Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Oct 2019 22:35:49 -0400 Subject: [PATCH 10/35] attempt at adding 2Q hamiltonian canonicalization --- NOT READY FOR PRIME TIME --- cl-quil.asd | 1 + src/build-gate.lisp | 13 +++++-- src/compilers/pauli-pair.lisp | 72 +++++++++++++++++++++++++++++++++++ src/define-compiler.lisp | 3 +- src/gates.lisp | 39 ++++++++++--------- src/parser.lisp | 13 +++---- 6 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 src/compilers/pauli-pair.lisp diff --git a/cl-quil.asd b/cl-quil.asd index 3502624e3..69d140873 100644 --- a/cl-quil.asd +++ b/cl-quil.asd @@ -80,6 +80,7 @@ (:file "state-prep") (:file "translators") (:file "modifiers") + (:file "pauli-pair") ;; attic'd files / pedagogical purposes only (:file "optimal-2q") (:static-file "cs-compile"))) diff --git a/src/build-gate.lisp b/src/build-gate.lisp index e9ba3a05d..8d7e06ad2 100644 --- a/src/build-gate.lisp +++ b/src/build-gate.lisp @@ -54,15 +54,18 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build (define-global-counter **anonymous-gate-counter** get-anonymous-gate-counter) -(defun anon-gate (operator matrix qubit &rest qubits) +(defun anon-gate (operator gate qubit &rest qubits) "Variant of BUILD-GATE for constructing anonymous gate applications." (check-type operator string) - (check-type matrix magicl:matrix) (push qubit qubits) - (let ((name (format nil "~A-~A" operator (get-anonymous-gate-counter)))) + (let* ((name (format nil "~A-~A" operator (get-anonymous-gate-counter))) + (gate + (etypecase gate + (gate gate) + (magicl:matrix (make-instance 'simple-gate :matrix gate :name name))))) (make-instance 'gate-application :operator (named-operator name) - :gate (make-instance 'simple-gate :matrix matrix :name name) + :gate gate :arguments (mapcar #'%capture-arg qubits)))) (defun repeatedly-fork (op n) @@ -74,6 +77,8 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build (apply #'build-gate (repeatedly-fork (named-operator roll-name) (length qubits)) params qubit qubits)) +(defun pauli-gate (operator &rest pauli-)) + ;;; functions for dealing with mixed constant vs delayed-expression types (defun param-binary-op (op arg1 arg2) diff --git a/src/compilers/pauli-pair.lisp b/src/compilers/pauli-pair.lisp new file mode 100644 index 000000000..31e7320eb --- /dev/null +++ b/src/compilers/pauli-pair.lisp @@ -0,0 +1,72 @@ +;;;; pauli-pair.lisp +;;;; +;;;; Author: Eric Peterson +;;;; +;;;; This file contains routines for the parametric compilation of two-qubit +;;;; gates determined by time-independent Hamiltonians via expression as a +;;;; PAULI-SUM-GATE. + +(in-package #:cl-quil) + +#+ignore +(define-compiler canonical-hamiltonian-compiler + ((instr (_ _ q1 q0) + :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) + (loop :for term :in (pauli-sum-gate-terms (gate-application-gate instr)) + :always (or (string= "XX" (pauli-term-pauli-word term)) + (string= "YY" (pauli-term-pauli-word term)) + (string= "ZZ" (pauli-term-pauli-word term))))))) + "Parametric compiler to CZs for time-independent canonical Hamiltonians." + + ) + +(define-compiler pauli-pair-compiler + ((instr (_ (time) q1 q0) ; name params q1 q0 + :where (and (not (typep time 'number)) + (typep (gate-application-gate instr) 'pauli-sum-gate)))) + "Rewrites a parametric 2Q gate application described by a time-independent Hamiltonian into canonical form." + (let ((gate (gate-application-gate instr))) + ;; XXX: check that the hamiltonian H(t) is time-independent (i.e., time-linear) + ;; instantiate the hamiltonian H0 = H(1) at a particular time + (let* ((H0 (gate-matrix gate 1.0d0))) + ;; diagonalize it: H0 = U D U* + (multiple-value-bind (d u) (magicl:eig H0) + (format t "~&diagonal phases: ~a~%" (mapcar #'phase d)) + (let* (;; infer a presentation of D as a hamiltonian: D = EXPI(a ZI + b IZ + c ZZ) + (kernel (print (m* (magicl:inv (make-row-major-matrix 3 3 (list -1 1 -1 + -1 -1 1 + 1 1 1))) + (make-row-major-matrix 3 1 (mapcar #'phase (rest d)))))) + (ZI (magicl:ref kernel 0 0)) + (IZ (magicl:ref kernel 1 0)) + (ZZ (magicl:ref kernel 2 0)) + ;; use +e-basis+ to turn the middle into a canonical gate: + ;; H0 = UDU* = UE* EDE* EU* and EDE* = EXPI(a XX + b YY + c ZZ) + (formal-qubit-args (list (formal "q1") (formal "q0"))) + (formal-parameter-name (make-symbol "time")) + (canonical-hamiltonian + (make-instance 'pauli-sum-gate + :arguments formal-qubit-args + :parameters (list formal-parameter-name) + :terms (list (make-pauli-term :pauli-word "XX" + :prefactor `(* ,ZI ,formal-parameter-name) + :arguments formal-qubit-args) + (make-pauli-term :pauli-word "YY" + :prefactor `(* ,IZ ,formal-parameter-name) + :arguments formal-qubit-args) + (make-pauli-term :pauli-word "ZZ" + :prefactor `(* ,ZZ ,formal-parameter-name) + :arguments formal-qubit-args)) + :dimension 4 + :arity 1 + :name "CANONICALIZED-HAM")) + ;; return: anonymous gate (UE*) canonical hamiltonian (EDE*) anonymous gate (EU*) + (left-matrix (m* u +edag-basis+)) + (right-matrix (m* +e-basis+ (magicl:conjugate-transpose u)))) + (inst "CONJ-RIGHT" right-matrix q1 q0) + (inst (make-instance 'gate-application + :operator (named-operator "CANONICAL-AS-PAULI") + :arguments (list (qubit q1) (qubit q0)) + :parameters (list time) + :gate canonical-hamiltonian)) + (inst "CONJ-LEFT" left-matrix q1 q0)))))) diff --git a/src/define-compiler.lisp b/src/define-compiler.lisp index f48954465..c4e812f22 100644 --- a/src/define-compiler.lisp +++ b/src/define-compiler.lisp @@ -894,7 +894,8 @@ FINISH-COMPILER is a local macro usable within a compiler body." (setf ,x (apply #'build-gate ,xs))) ;; check for an anon-gate signature ((and (<= 3 (length ,xs)) - (typep (cadr ,xs) 'magicl:matrix)) + (or (typep (cadr ,xs) 'magicl:matrix) + (typep (cadr ,xs) 'gate))) (setf ,x (apply #'anon-gate ,xs))) (t (error "INST argument pattern not recognized: ~A" ,xs))) diff --git a/src/gates.lisp b/src/gates.lisp index 3c04f45ce..d06ce6fc7 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -176,6 +176,7 @@ :documentation "The minimal dimension of the space the gate acts on.") (matrix-function :initarg :matrix-function :reader parameterized-gate-matrix-function + :writer (setf %parameterized-gate-matrix-function) :documentation "Function mapping ARITY complex numbers to a DIMENSION x DIMENSION MAGICL matrix.")) (:documentation "A gate parameterized by complex numbers.")) @@ -190,9 +191,7 @@ (apply (parameterized-gate-matrix-function gate) parameters)) (defclass pauli-sum-gate (parameterized-gate) - ((dimension :initarg :dimension - :reader gate-dimension) - (parameters :initarg :parameters + ((parameters :initarg :parameters :reader pauli-sum-gate-parameters) (arguments :initarg :arguments :reader pauli-sum-gate-arguments) @@ -200,6 +199,19 @@ :reader pauli-sum-gate-terms)) (:documentation "A gate specified by a Pauli sum.")) +(defmethod initialize-instance :after ((gate pauli-sum-gate) &key) + (with-slots (parameters arguments terms) gate + (let ((size (expt 2 (length arguments)))) + (flet ((matrix-function (&rest params) + (assert (= (length parameters) (length params))) + (matrix-expt (reduce (lambda (m term) + (m+ m (pauli-term->matrix term arguments params parameters))) + terms + :initial-value (magicl:make-zero-matrix size size)) + (complex 0d0 -1d0)))) + (setf (%parameterized-gate-matrix-function gate) + #'matrix-function))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;; Gate Operators ;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Controlled Gates @@ -370,21 +382,12 @@ (defmethod gate-definition-to-gate ((gate-def pauli-sum-gate-definition)) (with-slots (arguments parameters terms) gate-def - (let ((size (expt 2 (length (pauli-sum-gate-definition-arguments gate-def))))) - (flet ((matrix-function (&rest params) - (assert (= (length parameters) (length params))) - (matrix-expt (reduce (lambda (m term) - (m+ m (pauli-term->matrix term arguments params parameters))) - terms - :initial-value (magicl:make-zero-matrix size size)) - (complex 0d0 -1d0)))) - (make-instance 'pauli-sum-gate - :arguments (pauli-sum-gate-definition-arguments gate-def) - :parameters (pauli-sum-gate-definition-parameters gate-def) - :terms (pauli-sum-gate-definition-terms gate-def) - :dimension size - :arity (length (pauli-sum-gate-definition-arguments gate-def)) - :matrix-function #'matrix-function))))) + (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :terms terms + :dimension (expt 2 (length arguments)) + :arity (length arguments)))) ;;;; some leftover stuff from standard-gates.lisp and elsewhere diff --git a/src/parser.lisp b/src/parser.lisp index 1e055c8a9..19ae6de9f 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -989,13 +989,12 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (defun pauli-term->matrix (term arguments parameters parameter-names) (let* ((prefactor-fn (typecase (pauli-term-prefactor term) - (number (lambda (&rest args) (declare (ignore args)) (pauli-term-prefactor term))) - (symbol (compile nil `(lambda ,parameter-names - (declare (ignorable ,@parameter-names)) - ,(pauli-term-prefactor term)))) - (cons (compile nil `(lambda ,parameter-names - (declare (ignorable ,@parameter-names)) - ,@(pauli-term-prefactor term)))))) + (number + (lambda (&rest args) (declare (ignore args)) (pauli-term-prefactor term))) + ((or symbol cons) + (compile nil `(lambda ,parameter-names + (declare (ignorable ,@parameter-names)) + ,(pauli-term-prefactor term)))))) (arg-count (length arguments)) (size (expt 2 arg-count)) (m (magicl:make-zero-matrix size size))) From f1cb6d775f1d53cc2c3a833be6a18fe73b152c77 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 07:34:39 -0700 Subject: [PATCH 11/35] permit explicit wildcard bindings in define-compiler --- src/define-compiler.lisp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/define-compiler.lisp b/src/define-compiler.lisp index c4e812f22..d7b876d63 100644 --- a/src/define-compiler.lisp +++ b/src/define-compiler.lisp @@ -818,7 +818,8 @@ N.B.: This routine is somewhat fragile, and highly creative compiler authors wil (assert (listp source)) (multiple-value-bind (source options) (cleave-options source) (cond - ((endp (cdr source)) + ((or (endp (cdr source)) + (wildcard-pattern-p (second source))) (make-wildcard-binding :name (first source) :options options)) (t From 88572d011ddd6f0bbf41d3b2073adad4c5a336a4 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 08:12:49 -0700 Subject: [PATCH 12/35] rework custom gate definition anon-gates --- src/build-gate.lisp | 20 ++++++++++++++------ src/define-compiler.lisp | 10 +++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/build-gate.lisp b/src/build-gate.lisp index 8d7e06ad2..8cfd37bbd 100644 --- a/src/build-gate.lisp +++ b/src/build-gate.lisp @@ -54,15 +54,23 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build (define-global-counter **anonymous-gate-counter** get-anonymous-gate-counter) -(defun anon-gate (operator gate qubit &rest qubits) +(defun anon-gate (operator-or-gate gate-or-parameters qubit &rest qubits) "Variant of BUILD-GATE for constructing anonymous gate applications." - (check-type operator string) (push qubit qubits) - (let* ((name (format nil "~A-~A" operator (get-anonymous-gate-counter))) + (let* ((name + (etypecase operator-or-gate + (string + (format nil "~A-~A" operator-or-gate (get-anonymous-gate-counter))) + (gate + (format nil "~A-~A" (gate-name operator-or-gate) (get-anonymous-gate-counter))))) (gate - (etypecase gate - (gate gate) - (magicl:matrix (make-instance 'simple-gate :matrix gate :name name))))) + (cond + ((typep operator-or-gate 'gate) + operator-or-gate) + ((typep gate-or-parameters 'magicl:matrix) + (make-instance 'simple-gate :matrix gate-or-parameters :name name)) + (t + (error "Cannot find gate definition."))))) (make-instance 'gate-application :operator (named-operator name) :gate gate diff --git a/src/define-compiler.lisp b/src/define-compiler.lisp index d7b876d63..cb80f82e7 100644 --- a/src/define-compiler.lisp +++ b/src/define-compiler.lisp @@ -889,15 +889,15 @@ FINISH-COMPILER is a local macro usable within a compiler body." ((and (= 1 (length ,xs)) (typep (first ,xs) 'gate-application)) (setf ,x (first ,xs))) - ;; check for a build-gate signature - ((and (<= 3 (length ,xs)) - (typep (cadr ,xs) 'list)) - (setf ,x (apply #'build-gate ,xs))) ;; check for an anon-gate signature ((and (<= 3 (length ,xs)) (or (typep (cadr ,xs) 'magicl:matrix) - (typep (cadr ,xs) 'gate))) + (typep (car ,xs) 'gate))) (setf ,x (apply #'anon-gate ,xs))) + ;; check for a build-gate signature + ((and (<= 3 (length ,xs)) + (typep (cadr ,xs) 'list)) + (setf ,x (apply #'build-gate ,xs))) (t (error "INST argument pattern not recognized: ~A" ,xs))) (rplacd ,tail (cons ,x nil)) From fb5062d5a4132ed6627c7e847dcc26845ac33c30 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 08:13:21 -0700 Subject: [PATCH 13/35] checkpoint: diagonal compiler sorta works --- src/compilers/pauli-pair.lisp | 307 ++++++++++++++++++++++++++++------ 1 file changed, 254 insertions(+), 53 deletions(-) diff --git a/src/compilers/pauli-pair.lisp b/src/compilers/pauli-pair.lisp index 31e7320eb..de9852212 100644 --- a/src/compilers/pauli-pair.lisp +++ b/src/compilers/pauli-pair.lisp @@ -2,26 +2,216 @@ ;;;; ;;;; Author: Eric Peterson ;;;; -;;;; This file contains routines for the parametric compilation of two-qubit -;;;; gates determined by time-independent Hamiltonians via expression as a +;;;; This file contains routines for the parametric compilation of gates defined +;;;; by time-independent Hamiltonians via expression as a ;;;; PAULI-SUM-GATE. (in-package #:cl-quil) -#+ignore -(define-compiler canonical-hamiltonian-compiler - ((instr (_ _ q1 q0) +;; WARNING: this consumes stack space like (* tree-depth fan-out) +(defun tree-substitute (big-tree substitution-table) + (cond + ((listp big-tree) + (mapcar (a:rcurry #'tree-substitute substitution-table) big-tree)) + ((assoc big-tree substitution-table) + (cdr (assoc big-tree substitution-table))) + (t + big-tree))) + +(define-compiler parametric-diagonal-compiler + ((instr _ :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) - (loop :for term :in (pauli-sum-gate-terms (gate-application-gate instr)) - :always (or (string= "XX" (pauli-term-pauli-word term)) - (string= "YY" (pauli-term-pauli-word term)) - (string= "ZZ" (pauli-term-pauli-word term))))))) - "Parametric compiler to CZs for time-independent canonical Hamiltonians." - - ) + (every (lambda (term) (every (lambda (letter) (or (eql letter #\Z) (eql letter #\I))) + (pauli-term-pauli-word term))) + (pauli-sum-gate-terms (gate-application-gate instr)))))) + "Decomposes a diagonal Pauli gate by a single step." + (declare (optimize (debug 3) (speed 0))) + (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) + (let ((nonlocal-terms nil)) + ;; first, deal with the words with a zero Zs / one Z: they're local gates + (dolist (term terms) + (multiple-value-bind (Z-count Z-position) + (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :with pos := 0 + :with count := 0 + :when (eql #\Z letter) + :do (setf count (1+ count) + pos j) + :finally (return (values count pos))) + (case Z-count + (0 + nil) + (1 + (inst "RZ" + (list (param-* 2.0d0 + (tree-substitute (pauli-term-prefactor term) + (mapcar #'cons parameters (application-parameters instr))))) + (nth (position (nth Z-position (pauli-term-arguments term)) + arguments :test #'equalp) + (application-arguments instr)))) + (otherwise + (push term nonlocal-terms))))) + (let ((votes (make-array (length arguments) :initial-element 0)) + vote) + ;; we can break nonlocal terms into two collections: those with Zs in + ;; some spot and those without Zs in that spot. the result will be to + ;; conjugate the first collection by CNOT, which flips those Zs to Is. + ;; since we can only emit local gates, almost all such Zs will have to + ;; be eliminated, and so we'll want to pick the position so that this + ;; group is as large as possible. + (dolist (term nonlocal-terms) + (loop :for letter :across (pauli-term-pauli-word term) + :for argument :in (pauli-term-arguments term) + :when (eql #\Z letter) + :do (incf (aref votes (position argument arguments :test #'equalp))))) + (loop :with pos := 0 + :with current-max := (aref votes 0) + :for j :from 0 + :for item :across votes + :when (< current-max item) + :do (setf current-max item + pos j) + :finally (setf vote current-max)) + ;; now we re-sort the nonlocal terms into two buckets: those with a Z in + ;; the voted-upon location, and those without + (let ((Is nil) (Zs nil)) + (dolist (term nonlocal-terms) + (let ((Z-pos (loop :for letter :across (pauli-term-pauli-word term) + :for arg :in (pauli-term-arguments term) + :for j :from 0 + :when (and (eql #\Z letter) + (eql vote (position arg arguments :test #'equalp))) + :do (return j)))) + (cond + (Z-pos + (push (list term Z-pos) Zs)) + (t + (push term Is))))) + ;; emit the Is + (unless (endp Is) + (let ((I-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :name "Is" + :terms Is))) + (inst* I-gate + (application-parameters instr) + (application-arguments instr)))) + ;; emit the Zs + (let* ((localized-terms + (loop :for (term Z-pos) :in Zs + :collect (make-pauli-term + :prefactor (pauli-term-prefactor term) + :arguments (pauli-term-arguments term) + :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :if (eql j Z-pos) + :collect #\I + :else + :collect letter) + 'string)))) + (Z-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :terms localized-terms + :name "Zs-GATE")) + (control-qubit (nth vote (application-arguments instr))) + ;; TODO: there's room here to make an intelligent choice of + ;; target qubit. they all act the same, so it'd be best to pick + ;; that one that's topologically nearest the control. + (target-qubit (if (zerop vote) + (second (application-arguments instr)) + (first (application-arguments instr))))) + (print (pauli-sum-gate-terms Z-gate)) + (inst "CNOT" () control-qubit target-qubit) + (inst* Z-gate + (application-parameters instr) + (application-arguments instr)) + (inst "CNOT" () control-qubit target-qubit))))))) + +;; i think that a generic diagonal gate +;; DIAG(alpha0, ..., alpha(2^n-1)) qn ... q0 +;; can be written as +;; FORKED ... FORKED RZ(a0, ..., a(2^(n-1)-1)) qn ... q0 +;; FORKED ... FORKED RZ(b0, ..., b(2^(n-2)-1)) qn ... q1 +;; ... +;; FORKED RZ(y0, y1) qn q(n-1) +;; RZ(z0) qn. +;; +;; the original gate has 2^n - 1 free parameters, neglecting global phase. +;; this sum has 2^(n-1) + 2^(n-2) + ... + 2 + 1 free parameters, which agrees, so that's good. +;; +;; i think i can give a state-prep-style calculation of the roman parameters from the greek parameters. +;; a better question is: is there a decomposition where the Pauli parameters directly show up? +;; +;; let's try a couple of smaller examples. +;; n = 0: a I + b Z = (a + b, a - b) +;; in this case, b turns into the 'difference' angle RZ(z0), and a is a global shift. +;; n = 1: a ZI + b IZ + c ZZ = (a + b + c, -a + b - c, a - b - c, -a - b + c) +;; the goal is to make this look like (e, e, -e, -e). +;; take e = b; then (a + c, -a - c, a - c, -a + c) + (b, b, -b, -b) +;; then (a + c, -a - c, a - c, -a + c) = (a, -a, a, -a) + (c, -c, -c, c) +;; then (c, -c, -c, c) = CNOT 1 0 ZI(c) CNOT 1 0 (i.e., the target matches the Z) +;; (a, -a, a, -a) = ZI(a), and (b, b, -b, -b) = IZ(b). +;; n = 2: a ZII + b IZI + c IIZ + d ZZI + e ZIZ + f IZZ + g ZZZ = +;; ZII(a) + IZI(b) + IIZ(c) + (d ZZI + e ZIZ + f IZZ + g ZZZ) +;; then (d ZZI + e ZIZ + f IZZ + g ZZZ) = +;; (d + e + f + g, -d - e + f - g, -d + e - f - g, d - e - f + g, +;; d - e - f - g, -d + e - f + g, -d - e + f + g, d + e + f - g) +;; ZZI = (1, -1, -1, 1, 1, -1, -1, 1) +;; ZIZ = (1, -1, 1, -1, -1, 1, -1, 1) +;; IZZ = (1, 1, -1, -1, -1, -1, 1, 1) +;; ZZZ = (1, -1, -1, 1, -1, 1, 1, -1) <-- CNOT 2 0 ; CNOT 1 0 ; RZ(g) 0 ; CNOT 1 0 ; CNOT 2 0 + + + +;; TODO: also write an orthogonal gate compiler somewhere? approx.lisp will take +;; care of it in the 2Q case, at least. +(define-compiler parametric-pauli-compiler + ((instr _ :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) + (= 1 (length (application-parameters instr))) + (not (typep (first (application-parameters instr)) 'constant))))) + "Decomposes a gate described by the exponential of a time-independent Hamiltonian into static orthogonal and parametric diagonal components." + (let ((gate (gate-application-gate instr))) + (with-slots (arguments parameters terms dimension) gate + ;; XXX: check that every component in the gate has coefficient of the form + ;; c*t for c a constant and t the application-parameter. + + ;; instantiate the Hamiltonian + (let ((H (magicl:make-zero-matrix dimension dimension))) + (dolist (term terms) + (setf m (m+ m (pauli-term->matrix term arguments (list 1d0) parameters)))) + ;; orthogonally diagonalize it: H = O D O^T + (multiple-value-bind (diagonal O) (magicl:eig H) + ;; TODO: build a diagonal Pauli sum + (let ((diagonal-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :dimension size + :terms ... + :arity 1 + :name (string (gensym "DIAG-PAULI-"))))) + (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) + (inst (make-instance 'gate-application + :gate diagonal-gate + :arguments (application-arguments instr) + :parameters (application-parameters instr) + :operator (named-operator (string (gensym "DIAG-INSTR-"))))) + (inst* "LEFT-O" O (application-arguments instr)))))))) + + + +;; TODO: consider whether you should be extracting the Hamiltonian from sampling +;; the unitary family or if you should be applying EIG to the pauli sum directly. +#+ignore (define-compiler pauli-pair-compiler - ((instr (_ (time) q1 q0) ; name params q1 q0 + ((instr (_ (time) q1 q0) ; name params q1 q0 :where (and (not (typep time 'number)) (typep (gate-application-gate instr) 'pauli-sum-gate)))) "Rewrites a parametric 2Q gate application described by a time-independent Hamiltonian into canonical form." @@ -30,43 +220,54 @@ ;; instantiate the hamiltonian H0 = H(1) at a particular time (let* ((H0 (gate-matrix gate 1.0d0))) ;; diagonalize it: H0 = U D U* - (multiple-value-bind (d u) (magicl:eig H0) - (format t "~&diagonal phases: ~a~%" (mapcar #'phase d)) - (let* (;; infer a presentation of D as a hamiltonian: D = EXPI(a ZI + b IZ + c ZZ) - (kernel (print (m* (magicl:inv (make-row-major-matrix 3 3 (list -1 1 -1 - -1 -1 1 - 1 1 1))) - (make-row-major-matrix 3 1 (mapcar #'phase (rest d)))))) - (ZI (magicl:ref kernel 0 0)) - (IZ (magicl:ref kernel 1 0)) - (ZZ (magicl:ref kernel 2 0)) - ;; use +e-basis+ to turn the middle into a canonical gate: - ;; H0 = UDU* = UE* EDE* EU* and EDE* = EXPI(a XX + b YY + c ZZ) - (formal-qubit-args (list (formal "q1") (formal "q0"))) - (formal-parameter-name (make-symbol "time")) - (canonical-hamiltonian - (make-instance 'pauli-sum-gate - :arguments formal-qubit-args - :parameters (list formal-parameter-name) - :terms (list (make-pauli-term :pauli-word "XX" - :prefactor `(* ,ZI ,formal-parameter-name) - :arguments formal-qubit-args) - (make-pauli-term :pauli-word "YY" - :prefactor `(* ,IZ ,formal-parameter-name) - :arguments formal-qubit-args) - (make-pauli-term :pauli-word "ZZ" - :prefactor `(* ,ZZ ,formal-parameter-name) - :arguments formal-qubit-args)) - :dimension 4 - :arity 1 - :name "CANONICALIZED-HAM")) - ;; return: anonymous gate (UE*) canonical hamiltonian (EDE*) anonymous gate (EU*) - (left-matrix (m* u +edag-basis+)) - (right-matrix (m* +e-basis+ (magicl:conjugate-transpose u)))) - (inst "CONJ-RIGHT" right-matrix q1 q0) - (inst (make-instance 'gate-application - :operator (named-operator "CANONICAL-AS-PAULI") - :arguments (list (qubit q1) (qubit q0)) - :parameters (list time) - :gate canonical-hamiltonian)) - (inst "CONJ-LEFT" left-matrix q1 q0)))))) + (multiple-value-bind (dd u) (magicl:eig H0) + ;; canonicalize d and u so that the phases of d are sorted ascending. + ;; XXX: also deal with equivalence in d up to multiplication by i + (let* ((pairs (loop :for d :in dd + :for j :below 4 + :for row := (loop :for i :below 4 :collect (magicl:ref u j i)) + :collect (list d row))) + (sorted-pairs (sort pairs #'< :key (a:compose #'phase #'car))) + (dd (mapcar #'car sorted-pairs)) + (u (make-row-major-matrix 4 4 (loop :for (phase row) :in sorted-pairs + :nconc row)))) + ;; XXX: if U escaped SU(4), add a phase shift to put it back in. + (format t "~&diagonal phases: ~a~%" (mapcar #'phase dd)) + (let* (;; infer a presentation of D as a hamiltonian: D = EXPI(a ZI + b IZ + c ZZ) + (kernel (print (m* (magicl:inv (make-row-major-matrix 3 3 (list -1 1 -1 + -1 -1 1 + 1 1 1))) + (make-row-major-matrix 3 1 (mapcar #'phase (rest dd)))))) + (ZI (magicl:ref kernel 0 0)) + (IZ (magicl:ref kernel 1 0)) + (ZZ (magicl:ref kernel 2 0)) + ;; use +e-basis+ to turn the middle into a canonical gate: + ;; H0 = UDU* = UE* EDE* EU* and EDE* = EXPI(a XX + b YY + c ZZ) + (formal-qubit-args (list (formal "q1") (formal "q0"))) + (formal-parameter-name (make-symbol "time")) + (canonical-hamiltonian + (make-instance 'pauli-sum-gate + :arguments formal-qubit-args + :parameters (list formal-parameter-name) + :terms (list (make-pauli-term :pauli-word "XX" + :prefactor `(* ,ZI ,formal-parameter-name) + :arguments formal-qubit-args) + (make-pauli-term :pauli-word "YY" + :prefactor `(* ,IZ ,formal-parameter-name) + :arguments formal-qubit-args) + (make-pauli-term :pauli-word "ZZ" + :prefactor `(* ,ZZ ,formal-parameter-name) + :arguments formal-qubit-args)) + :dimension 4 + :arity 1 + :name "CANONICALIZED-HAM")) + ;; return: anonymous gate (UE*) canonical hamiltonian (EDE*) anonymous gate (EU*) + (left-matrix (m* u +edag-basis+)) + (right-matrix (m* +e-basis+ (magicl:conjugate-transpose u)))) + (inst "CONJ-RIGHT" right-matrix q1 q0) + (inst (make-instance 'gate-application + :operator (named-operator "CANONICAL-AS-PAULI") + :arguments (list (qubit q1) (qubit q0)) + :parameters (list time) + :gate canonical-hamiltonian)) + (inst "CONJ-LEFT" left-matrix q1 q0))))))) From 7b2f0357944de5ceea9bf35f24f93ca382b66c5c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 11:07:02 -0700 Subject: [PATCH 14/35] allow custom gate anon-gates to have parameter lists --- src/build-gate.lisp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/build-gate.lisp b/src/build-gate.lisp index 8cfd37bbd..9e724c1df 100644 --- a/src/build-gate.lisp +++ b/src/build-gate.lisp @@ -70,11 +70,16 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build ((typep gate-or-parameters 'magicl:matrix) (make-instance 'simple-gate :matrix gate-or-parameters :name name)) (t - (error "Cannot find gate definition."))))) + (error "Cannot find gate definition.")))) + (parameters + (typecase gate-or-parameters + (cons gate-or-parameters) + (otherwise nil)))) (make-instance 'gate-application :operator (named-operator name) :gate gate - :arguments (mapcar #'%capture-arg qubits)))) + :arguments (mapcar #'%capture-arg qubits) + :parameters parameters))) (defun repeatedly-fork (op n) (loop :repeat n From 3a11de86c8cab7453132fa21fdb1e897344d73f8 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 11:07:24 -0700 Subject: [PATCH 15/35] kill needless newline --- src/gates.lisp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gates.lisp b/src/gates.lisp index d06ce6fc7..c852be71d 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -209,8 +209,7 @@ terms :initial-value (magicl:make-zero-matrix size size)) (complex 0d0 -1d0)))) - (setf (%parameterized-gate-matrix-function gate) - #'matrix-function))))) + (setf (%parameterized-gate-matrix-function gate) #'matrix-function))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;; Gate Operators ;;;;;;;;;;;;;;;;;;;;;;;;;;; From 9b5a0f0f13ce83b3a98c4b99a805dcb5fbfa9d44 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 11:07:44 -0700 Subject: [PATCH 16/35] sort out delayed-expression tricks, mostly --- src/compilers/pauli-pair.lisp | 235 ++++++++++++---------------------- 1 file changed, 84 insertions(+), 151 deletions(-) diff --git a/src/compilers/pauli-pair.lisp b/src/compilers/pauli-pair.lisp index de9852212..41feed419 100644 --- a/src/compilers/pauli-pair.lisp +++ b/src/compilers/pauli-pair.lisp @@ -13,11 +13,18 @@ (cond ((listp big-tree) (mapcar (a:rcurry #'tree-substitute substitution-table) big-tree)) + ((delayed-expression-p big-tree) + (make-delayed-expression + (delayed-expression-params big-tree) + (delayed-expression-lambda-params big-tree) + (tree-substitute (delayed-expression-expression big-tree) substitution-table))) ((assoc big-tree substitution-table) (cdr (assoc big-tree substitution-table))) (t big-tree))) +;; WARNING: i don't know where this method has been considered before, and i +;; myself haven't worked out a proof that it's correct. caveat compiler (define-compiler parametric-diagonal-compiler ((instr _ :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) @@ -46,7 +53,13 @@ (inst "RZ" (list (param-* 2.0d0 (tree-substitute (pauli-term-prefactor term) - (mapcar #'cons parameters (application-parameters instr))))) + (mapcar (lambda (ep ap) + (typecase ap + (delayed-expression + (cons ep (delayed-expression-expression ap))) + (otherwise + (cons ep ap)))) + parameters (application-parameters instr))))) (nth (position (nth Z-position (pauli-term-arguments term)) arguments :test #'equalp) (application-arguments instr)))) @@ -101,73 +114,38 @@ (application-parameters instr) (application-arguments instr)))) ;; emit the Zs - (let* ((localized-terms - (loop :for (term Z-pos) :in Zs - :collect (make-pauli-term - :prefactor (pauli-term-prefactor term) - :arguments (pauli-term-arguments term) - :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :if (eql j Z-pos) - :collect #\I - :else - :collect letter) - 'string)))) - (Z-gate (make-instance 'pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :terms localized-terms - :name "Zs-GATE")) - (control-qubit (nth vote (application-arguments instr))) - ;; TODO: there's room here to make an intelligent choice of - ;; target qubit. they all act the same, so it'd be best to pick - ;; that one that's topologically nearest the control. - (target-qubit (if (zerop vote) - (second (application-arguments instr)) - (first (application-arguments instr))))) - (print (pauli-sum-gate-terms Z-gate)) - (inst "CNOT" () control-qubit target-qubit) - (inst* Z-gate - (application-parameters instr) - (application-arguments instr)) - (inst "CNOT" () control-qubit target-qubit))))))) - -;; i think that a generic diagonal gate -;; DIAG(alpha0, ..., alpha(2^n-1)) qn ... q0 -;; can be written as -;; FORKED ... FORKED RZ(a0, ..., a(2^(n-1)-1)) qn ... q0 -;; FORKED ... FORKED RZ(b0, ..., b(2^(n-2)-1)) qn ... q1 -;; ... -;; FORKED RZ(y0, y1) qn q(n-1) -;; RZ(z0) qn. -;; -;; the original gate has 2^n - 1 free parameters, neglecting global phase. -;; this sum has 2^(n-1) + 2^(n-2) + ... + 2 + 1 free parameters, which agrees, so that's good. -;; -;; i think i can give a state-prep-style calculation of the roman parameters from the greek parameters. -;; a better question is: is there a decomposition where the Pauli parameters directly show up? -;; -;; let's try a couple of smaller examples. -;; n = 0: a I + b Z = (a + b, a - b) -;; in this case, b turns into the 'difference' angle RZ(z0), and a is a global shift. -;; n = 1: a ZI + b IZ + c ZZ = (a + b + c, -a + b - c, a - b - c, -a - b + c) -;; the goal is to make this look like (e, e, -e, -e). -;; take e = b; then (a + c, -a - c, a - c, -a + c) + (b, b, -b, -b) -;; then (a + c, -a - c, a - c, -a + c) = (a, -a, a, -a) + (c, -c, -c, c) -;; then (c, -c, -c, c) = CNOT 1 0 ZI(c) CNOT 1 0 (i.e., the target matches the Z) -;; (a, -a, a, -a) = ZI(a), and (b, b, -b, -b) = IZ(b). -;; n = 2: a ZII + b IZI + c IIZ + d ZZI + e ZIZ + f IZZ + g ZZZ = -;; ZII(a) + IZI(b) + IIZ(c) + (d ZZI + e ZIZ + f IZZ + g ZZZ) -;; then (d ZZI + e ZIZ + f IZZ + g ZZZ) = -;; (d + e + f + g, -d - e + f - g, -d + e - f - g, d - e - f + g, -;; d - e - f - g, -d + e - f + g, -d - e + f + g, d + e + f - g) -;; ZZI = (1, -1, -1, 1, 1, -1, -1, 1) -;; ZIZ = (1, -1, 1, -1, -1, 1, -1, 1) -;; IZZ = (1, 1, -1, -1, -1, -1, 1, 1) -;; ZZZ = (1, -1, -1, 1, -1, 1, 1, -1) <-- CNOT 2 0 ; CNOT 1 0 ; RZ(g) 0 ; CNOT 1 0 ; CNOT 2 0 - + (unless (endp Zs) + (let* ((localized-terms + (loop :for (term Z-pos) :in Zs + :collect (make-pauli-term + :prefactor (pauli-term-prefactor term) + :arguments (pauli-term-arguments term) + :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :if (eql j Z-pos) + :collect #\I + :else + :collect letter) + 'string)))) + (Z-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :terms localized-terms + :name "Zs-GATE")) + (control-qubit (nth vote (application-arguments instr))) + ;; TODO: there's room here to make an intelligent choice of + ;; target qubit. they all act the same, so it'd be best to pick + ;; that one that's topologically nearest the control. + (target-qubit (if (zerop vote) + (second (application-arguments instr)) + (first (application-arguments instr))))) + (inst "CNOT" () control-qubit target-qubit) + (inst* Z-gate + (application-parameters instr) + (application-arguments instr)) + (inst "CNOT" () control-qubit target-qubit)))))))) ;; TODO: also write an orthogonal gate compiler somewhere? approx.lisp will take @@ -186,88 +164,43 @@ ;; instantiate the Hamiltonian (let ((H (magicl:make-zero-matrix dimension dimension))) (dolist (term terms) - (setf m (m+ m (pauli-term->matrix term arguments (list 1d0) parameters)))) + (setf H (m+ H (pauli-term->matrix term arguments (list 1d0) parameters)))) ;; orthogonally diagonalize it: H = O D O^T (multiple-value-bind (diagonal O) (magicl:eig H) - ;; TODO: build a diagonal Pauli sum - (let ((diagonal-gate (make-instance 'pauli-sum-gate - :arguments arguments - :parameters parameters - :dimension size - :terms ... - :arity 1 - :name (string (gensym "DIAG-PAULI-"))))) - (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) - (inst (make-instance 'gate-application - :gate diagonal-gate - :arguments (application-arguments instr) - :parameters (application-parameters instr) - :operator (named-operator (string (gensym "DIAG-INSTR-"))))) - (inst* "LEFT-O" O (application-arguments instr)))))))) - - - -;; TODO: consider whether you should be extracting the Hamiltonian from sampling -;; the unitary family or if you should be applying EIG to the pauli sum directly. -#+ignore -(define-compiler pauli-pair-compiler - ((instr (_ (time) q1 q0) ; name params q1 q0 - :where (and (not (typep time 'number)) - (typep (gate-application-gate instr) 'pauli-sum-gate)))) - "Rewrites a parametric 2Q gate application described by a time-independent Hamiltonian into canonical form." - (let ((gate (gate-application-gate instr))) - ;; XXX: check that the hamiltonian H(t) is time-independent (i.e., time-linear) - ;; instantiate the hamiltonian H0 = H(1) at a particular time - (let* ((H0 (gate-matrix gate 1.0d0))) - ;; diagonalize it: H0 = U D U* - (multiple-value-bind (dd u) (magicl:eig H0) - ;; canonicalize d and u so that the phases of d are sorted ascending. - ;; XXX: also deal with equivalence in d up to multiplication by i - (let* ((pairs (loop :for d :in dd - :for j :below 4 - :for row := (loop :for i :below 4 :collect (magicl:ref u j i)) - :collect (list d row))) - (sorted-pairs (sort pairs #'< :key (a:compose #'phase #'car))) - (dd (mapcar #'car sorted-pairs)) - (u (make-row-major-matrix 4 4 (loop :for (phase row) :in sorted-pairs - :nconc row)))) - ;; XXX: if U escaped SU(4), add a phase shift to put it back in. - (format t "~&diagonal phases: ~a~%" (mapcar #'phase dd)) - (let* (;; infer a presentation of D as a hamiltonian: D = EXPI(a ZI + b IZ + c ZZ) - (kernel (print (m* (magicl:inv (make-row-major-matrix 3 3 (list -1 1 -1 - -1 -1 1 - 1 1 1))) - (make-row-major-matrix 3 1 (mapcar #'phase (rest dd)))))) - (ZI (magicl:ref kernel 0 0)) - (IZ (magicl:ref kernel 1 0)) - (ZZ (magicl:ref kernel 2 0)) - ;; use +e-basis+ to turn the middle into a canonical gate: - ;; H0 = UDU* = UE* EDE* EU* and EDE* = EXPI(a XX + b YY + c ZZ) - (formal-qubit-args (list (formal "q1") (formal "q0"))) - (formal-parameter-name (make-symbol "time")) - (canonical-hamiltonian - (make-instance 'pauli-sum-gate - :arguments formal-qubit-args - :parameters (list formal-parameter-name) - :terms (list (make-pauli-term :pauli-word "XX" - :prefactor `(* ,ZI ,formal-parameter-name) - :arguments formal-qubit-args) - (make-pauli-term :pauli-word "YY" - :prefactor `(* ,IZ ,formal-parameter-name) - :arguments formal-qubit-args) - (make-pauli-term :pauli-word "ZZ" - :prefactor `(* ,ZZ ,formal-parameter-name) - :arguments formal-qubit-args)) - :dimension 4 - :arity 1 - :name "CANONICALIZED-HAM")) - ;; return: anonymous gate (UE*) canonical hamiltonian (EDE*) anonymous gate (EU*) - (left-matrix (m* u +edag-basis+)) - (right-matrix (m* +e-basis+ (magicl:conjugate-transpose u)))) - (inst "CONJ-RIGHT" right-matrix q1 q0) - (inst (make-instance 'gate-application - :operator (named-operator "CANONICAL-AS-PAULI") - :arguments (list (qubit q1) (qubit q0)) - :parameters (list time) - :gate canonical-hamiltonian)) - (inst "CONJ-LEFT" left-matrix q1 q0))))))) + ;; convert diagonal into a sum of Z paulis + (let ((pauli-prefactors (make-array dimension :initial-element 0d0)) + terms diagonal-gate) + (loop :for d :in diagonal + :for i :from 0 + :do (dotimes (j dimension) + (incf (aref pauli-prefactors j) + (if (evenp (logcount (logand i j))) + (/ d dimension) + (/ (- d) dimension))))) + (setf terms (loop :for prefactor :across pauli-prefactors + :for j :from 0 + :unless (double= 0d0 prefactor) + :collect (let ((term-arguments + (loop :for i :below (length arguments) + :for arg :in arguments + :when (logbitp (- (length arguments) i 1) j) + :collect arg))) + (make-pauli-term + :prefactor (param-* (realpart prefactor) + (make-delayed-expression + nil nil (first parameters))) + :arguments term-arguments + :pauli-word (coerce (make-array (length term-arguments) + :initial-element #\Z) + 'string)))) + diagonal-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :terms terms + :arity 1 + :dimension dimension + :name (string (gensym "DIAG-PAULI-")))) + ;; emit the instructions + (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) + (inst* diagonal-gate (application-parameters instr) (application-arguments instr)) + (inst* "LEFT-O" O (application-arguments instr)))))))) From 6ccb83cc0742f80c7bbf16859d5d696d82df8645 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 5 Oct 2019 11:09:58 -0700 Subject: [PATCH 17/35] change name to linear-paulis --- cl-quil.asd | 1 + src/compilers/{pauli-pair.lisp => linear-paulis.lisp} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename src/compilers/{pauli-pair.lisp => linear-paulis.lisp} (99%) diff --git a/cl-quil.asd b/cl-quil.asd index 69d140873..fdd846106 100644 --- a/cl-quil.asd +++ b/cl-quil.asd @@ -81,6 +81,7 @@ (:file "translators") (:file "modifiers") (:file "pauli-pair") + (:file "linear-paulis") ;; attic'd files / pedagogical purposes only (:file "optimal-2q") (:static-file "cs-compile"))) diff --git a/src/compilers/pauli-pair.lisp b/src/compilers/linear-paulis.lisp similarity index 99% rename from src/compilers/pauli-pair.lisp rename to src/compilers/linear-paulis.lisp index 41feed419..53884dac0 100644 --- a/src/compilers/pauli-pair.lisp +++ b/src/compilers/linear-paulis.lisp @@ -1,4 +1,4 @@ -;;;; pauli-pair.lisp +;;;; linear-paulis.lisp ;;;; ;;;; Author: Eric Peterson ;;;; From fa33101fdde932253dd7a6eb5eb80dd464f38fc0 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 14 Oct 2019 16:46:11 -0400 Subject: [PATCH 18/35] fix diagonal compiler --- REQUIRES MAGICL BRANCH --- src/compilers/linear-paulis.lisp | 184 +++++++++++++++++-------------- src/gates.lisp | 3 +- src/matrix-operations.lisp | 27 ++++- 3 files changed, 130 insertions(+), 84 deletions(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index 53884dac0..d36383172 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -23,6 +23,16 @@ (t big-tree))) +(defun array-argmax (arr) + (loop :with pos := 0 + :with current-max := (aref arr 0) + :for j :from 0 + :for item :across arr + :when (< current-max item) + :do (setf current-max item + pos j) + :finally (return pos))) + ;; WARNING: i don't know where this method has been considered before, and i ;; myself haven't worked out a proof that it's correct. caveat compiler (define-compiler parametric-diagonal-compiler @@ -32,7 +42,6 @@ (pauli-term-pauli-word term))) (pauli-sum-gate-terms (gate-application-gate instr)))))) "Decomposes a diagonal Pauli gate by a single step." - (declare (optimize (debug 3) (speed 0))) (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) (let ((nonlocal-terms nil)) ;; first, deal with the words with a zero Zs / one Z: they're local gates @@ -66,7 +75,8 @@ (otherwise (push term nonlocal-terms))))) (let ((votes (make-array (length arguments) :initial-element 0)) - vote) + vote + control-qubit) ;; we can break nonlocal terms into two collections: those with Zs in ;; some spot and those without Zs in that spot. the result will be to ;; conjugate the first collection by CNOT, which flips those Zs to Is. @@ -78,17 +88,11 @@ :for argument :in (pauli-term-arguments term) :when (eql #\Z letter) :do (incf (aref votes (position argument arguments :test #'equalp))))) - (loop :with pos := 0 - :with current-max := (aref votes 0) - :for j :from 0 - :for item :across votes - :when (< current-max item) - :do (setf current-max item - pos j) - :finally (setf vote current-max)) + (setf vote (array-argmax votes) + control-qubit (nth vote (application-arguments instr))) ;; now we re-sort the nonlocal terms into two buckets: those with a Z in ;; the voted-upon location, and those without - (let ((Is nil) (Zs nil)) + (let* ((Is nil) (Zs nil)) (dolist (term nonlocal-terms) (let ((Z-pos (loop :for letter :across (pauli-term-pauli-word term) :for arg :in (pauli-term-arguments term) @@ -114,38 +118,55 @@ (application-parameters instr) (application-arguments instr)))) ;; emit the Zs - (unless (endp Zs) - (let* ((localized-terms - (loop :for (term Z-pos) :in Zs - :collect (make-pauli-term - :prefactor (pauli-term-prefactor term) - :arguments (pauli-term-arguments term) - :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :if (eql j Z-pos) - :collect #\I - :else - :collect letter) - 'string)))) - (Z-gate (make-instance 'pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :terms localized-terms - :name "Zs-GATE")) - (control-qubit (nth vote (application-arguments instr))) - ;; TODO: there's room here to make an intelligent choice of - ;; target qubit. they all act the same, so it'd be best to pick - ;; that one that's topologically nearest the control. - (target-qubit (if (zerop vote) - (second (application-arguments instr)) - (first (application-arguments instr))))) - (inst "CNOT" () control-qubit target-qubit) - (inst* Z-gate - (application-parameters instr) - (application-arguments instr)) - (inst "CNOT" () control-qubit target-qubit)))))))) + (labels + ((collect-by-target (term-pairs) + (when (endp term-pairs) + (return-from collect-by-target nil)) + (let ((subvotes (make-array (length arguments)))) + (loop :for (term Z-pos) :in term-pairs + :do (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :for arg :in (pauli-term-arguments term) + :when (and (not (eql j Z-pos)) + (eql #\Z letter)) + :do (incf (aref subvotes (position arg arguments :test #'equalp))))) + (let* ((subvote-position (array-argmax subvotes)) + (subvote-formal (nth subvote-position arguments)) + (subvote-literal (nth subvote-position (application-arguments instr))) + ZZs ZIs) + (dolist (term-pair term-pairs) + (let ((term (car term-pair))) + (cond + ((position subvote-formal (pauli-term-arguments term) :test #'equalp) + (push term-pair ZZs)) + (t + (push term-pair ZIs))))) + (let* ((ZZ-terms + (loop :for (term Z-pos) :in ZZs + :collect (make-pauli-term + :prefactor (pauli-term-prefactor term) + :arguments (pauli-term-arguments term) + :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :if (eql j Z-pos) + :collect #\I + :else + :collect letter) + 'string)))) + (Z-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :terms ZZ-terms + :name "ZZ-GATE"))) + (inst "CNOT" () control-qubit subvote-literal) + (inst* Z-gate + (application-parameters instr) + (application-arguments instr)) + (inst "CNOT" () control-qubit subvote-literal)) + (collect-by-target ZIs))))) + (collect-by-target Zs))))))) ;; TODO: also write an orthogonal gate compiler somewhere? approx.lisp will take @@ -166,41 +187,42 @@ (dolist (term terms) (setf H (m+ H (pauli-term->matrix term arguments (list 1d0) parameters)))) ;; orthogonally diagonalize it: H = O D O^T - (multiple-value-bind (diagonal O) (magicl:eig H) - ;; convert diagonal into a sum of Z paulis - (let ((pauli-prefactors (make-array dimension :initial-element 0d0)) - terms diagonal-gate) - (loop :for d :in diagonal - :for i :from 0 - :do (dotimes (j dimension) - (incf (aref pauli-prefactors j) - (if (evenp (logcount (logand i j))) - (/ d dimension) - (/ (- d) dimension))))) - (setf terms (loop :for prefactor :across pauli-prefactors - :for j :from 0 - :unless (double= 0d0 prefactor) - :collect (let ((term-arguments - (loop :for i :below (length arguments) - :for arg :in arguments - :when (logbitp (- (length arguments) i 1) j) - :collect arg))) - (make-pauli-term - :prefactor (param-* (realpart prefactor) - (make-delayed-expression - nil nil (first parameters))) - :arguments term-arguments - :pauli-word (coerce (make-array (length term-arguments) - :initial-element #\Z) - 'string)))) - diagonal-gate (make-instance 'pauli-sum-gate - :arguments arguments - :parameters parameters - :terms terms - :arity 1 - :dimension dimension - :name (string (gensym "DIAG-PAULI-")))) - ;; emit the instructions - (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) - (inst* diagonal-gate (application-parameters instr) (application-arguments instr)) - (inst* "LEFT-O" O (application-arguments instr)))))))) + (multiple-value-bind (diagonal O) (magicl:hermitian-eig H) + (let ((O (orthonormalize-matrix O))) + ;; convert diagonal into a sum of Z paulis + (let ((pauli-prefactors (make-array dimension :initial-element 0d0)) + terms diagonal-gate) + (loop :for d :in diagonal + :for i :from 0 + :do (dotimes (j dimension) + (incf (aref pauli-prefactors j) + (if (evenp (logcount (logand i j))) + (/ d dimension) + (/ (- d) dimension))))) + (setf terms (loop :for prefactor :across pauli-prefactors + :for j :from 0 + :unless (double= 0d0 prefactor) + :collect (let ((term-arguments + (loop :for i :below (length arguments) + :for arg :in arguments + :when (logbitp (- (length arguments) i 1) j) + :collect arg))) + (make-pauli-term + :prefactor (param-* (realpart prefactor) + (make-delayed-expression + nil nil (first parameters))) + :arguments term-arguments + :pauli-word (coerce (make-array (length term-arguments) + :initial-element #\Z) + 'string)))) + diagonal-gate (make-instance 'pauli-sum-gate + :arguments arguments + :parameters parameters + :terms terms + :arity 1 + :dimension dimension + :name (string (gensym "DIAG-PAULI-")))) + ;; emit the instructions + (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) + (inst* diagonal-gate (application-parameters instr) (application-arguments instr)) + (inst* "LEFT-O" O (application-arguments instr))))))))) diff --git a/src/gates.lisp b/src/gates.lisp index c852be71d..7f01a42a3 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -208,7 +208,8 @@ (m+ m (pauli-term->matrix term arguments params parameters))) terms :initial-value (magicl:make-zero-matrix size size)) - (complex 0d0 -1d0)))) + (complex 0d0 -1d0) + :hermitian? t))) (setf (%parameterized-gate-matrix-function gate) #'matrix-function))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;; Gate Operators ;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/matrix-operations.lisp b/src/matrix-operations.lisp index 2bed4326b..bb3b21467 100644 --- a/src/matrix-operations.lisp +++ b/src/matrix-operations.lisp @@ -392,10 +392,33 @@ as needed so that they are the same size." kroned-ref-mat (scale-out-matrix-phases kroned-mat kroned-ref-mat)))))) -(defun matrix-expt (m s) +(defun matrix-expt (m s &key hermitian?) "Computes EXP(M*S). Only works for unitarily diagonalizable matrices M." - (multiple-value-bind (d u) (magicl:eig m) + (multiple-value-bind (d u) + (if hermitian? (magicl:hermitian-eig m) (magicl:eig m)) + (assert (matrix-equality m + (m* u + (magicl:diag (length d) (length d) d) + (magicl:conjugate-transpose u))) + () + "MATRIX-EXPT failed to diagonalize its input.") (let* ((size (length d)) (dd (magicl:diag size size (mapcar (lambda (z) (exp (* z s))) d)))) (m* u dd (magicl:conjugate-transpose u))))) + +(defun print-polar-matrix (m &optional (stream *standard-output*)) + (let ((*print-fractional-radians* nil) + (*print-polar-form* t) + (height (magicl::matrix-rows m)) + (width (magicl::matrix-cols m))) + (format stream "~&") + (dotimes (i height) + (dotimes (j width) + (let* ((z (magicl:ref m i j)) + (abs (if (double= 0d0 (abs z)) 0d0 (abs z))) + (phase (if (zerop abs) 0d0 (mod (phase z) (* 2 pi))))) + (format stream "~6,4F∠~6,4F" abs phase)) + (when (< j (1- width)) + (format stream ", "))) + (format stream "~%")))) From 83b73f88060d52bb52ce7485e7f94f13dbdfe5cd Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 15 Nov 2019 16:33:27 -0500 Subject: [PATCH 19/35] knock out two XXXs --- src/ast.lisp | 7 ++++--- src/compilers/linear-paulis.lisp | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/ast.lisp b/src/ast.lisp index 9eebeae15..4e94e7409 100644 --- a/src/ast.lisp +++ b/src/ast.lisp @@ -389,13 +389,14 @@ If no exit rewiring is found, return NIL." (defclass pauli-sum-gate-definition (gate-definition) ((terms :initarg :terms :reader pauli-sum-gate-definition-terms - :documentation "") ; XXX + :documentation "List of PAULI-TERMs comprising the sum.") (parameters :initarg :parameters :reader pauli-sum-gate-definition-parameters - :documentation "") ; XXX + :documentation "Ordered list of parameter names to be supplied to the definition, which can appear in arithmetical expressions weighting the definition's Pauli terms.") (arguments :initarg :arguments :reader pauli-sum-gate-definition-arguments - :documentation ""))) ; XXX + :documentation "Ordered list of formal arguments appearing in the definition's Pauli terms.")) + (:documentation "Represents a gate definition as the exponential of a weighted sum of Pauli matrices.")) (defmethod gate-definition-qubits-needed ((gate pauli-sum-gate-definition)) (length (pauli-sum-gate-definition-arguments gate))) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index d36383172..dc6eed29e 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -33,8 +33,6 @@ pos j) :finally (return pos))) -;; WARNING: i don't know where this method has been considered before, and i -;; myself haven't worked out a proof that it's correct. caveat compiler (define-compiler parametric-diagonal-compiler ((instr _ :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) @@ -179,8 +177,33 @@ "Decomposes a gate described by the exponential of a time-independent Hamiltonian into static orthogonal and parametric diagonal components." (let ((gate (gate-application-gate instr))) (with-slots (arguments parameters terms dimension) gate - ;; XXX: check that every component in the gate has coefficient of the form - ;; c*t for c a constant and t the application-parameter. + + (labels ((crawl-parameter (p) + (typecase p + (list + (case (first p) + (- + (unless (= 2 (length p)) + (give-up-compilation)) + (crawl-parameter (second p))) + (* + (unless (= 3 (length p)) + (give-up-compilation)) + (let ((total (+ (crawl-parameter (second p)) + (crawl-parameter (third p))))) + (unless (<= total 1) + (give-up-compilation)) + total)) + (otherwise + (give-up-compilation)))) + (number + 0) + (symbol + 1) + (otherwise + (give-up-compilation))))) + (dolist (term terms) + (crawl-parameter (pauli-term-prefactor term)))) ;; instantiate the Hamiltonian (let ((H (magicl:make-zero-matrix dimension dimension))) From 7f847468d6882622f1cd85a98a1359b400c9f8d3 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 15 Nov 2019 17:07:54 -0500 Subject: [PATCH 20/35] install global compilers --- src/chip/chip-specification.lisp | 2 ++ src/compilers/ucr-recognize.lisp | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/chip/chip-specification.lisp b/src/chip/chip-specification.lisp index 2ab285dda..79e07253f 100644 --- a/src/chip/chip-specification.lisp +++ b/src/chip/chip-specification.lisp @@ -393,6 +393,8 @@ used to specify CHIP-SPEC." (constantly 'state-prep-4q-compiler) (constantly 'state-prep-trampolining-compiler) (constantly 'recognize-ucr) + (constantly 'parametric-pauli-compiler) + (constantly 'parametric-diagonal-compiler) (constantly 'nearest-circuit-of-depth-0) (lambda (chip-spec arch) (declare (ignore chip-spec)) diff --git a/src/compilers/ucr-recognize.lisp b/src/compilers/ucr-recognize.lisp index b6d5f7629..27caff436 100644 --- a/src/compilers/ucr-recognize.lisp +++ b/src/compilers/ucr-recognize.lisp @@ -9,7 +9,10 @@ (define-compiler recognize-ucr ((instr :where (anonymous-gate-application-p instr))) "Checks whether an anonymous gate is a UCRY or UCRZ instruction, in which case it relabels it as such." - (let* ((matrix (gate-matrix instr)) + (let* ((matrix (handler-case (gate-matrix instr) + (unknown-gate-parameter (c) + (declare (ignore c)) + (give-up-compilation)))) (dimension (magicl:matrix-rows matrix)) (log-dimension (length (application-arguments instr))) angles) From 8a75d8f9ad34a8cbe6fc7ae70fffa39c970b4b9d Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 18 Nov 2019 10:47:27 -0500 Subject: [PATCH 21/35] remember to disallow constant terms --- src/compilers/linear-paulis.lisp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index dc6eed29e..2644f6e9e 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -203,7 +203,8 @@ (otherwise (give-up-compilation))))) (dolist (term terms) - (crawl-parameter (pauli-term-prefactor term)))) + (unless (= 1 (crawl-parameter (pauli-term-prefactor term))) + (give-up-compilation)))) ;; instantiate the Hamiltonian (let ((H (magicl:make-zero-matrix dimension dimension))) From 1d915ef7b0bc4e022a12310fa6f42fcd4d79102c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 9 Dec 2019 09:09:23 -0800 Subject: [PATCH 22/35] fix ASD misdefinition --- cl-quil.asd | 1 - 1 file changed, 1 deletion(-) diff --git a/cl-quil.asd b/cl-quil.asd index fdd846106..8ef2ba533 100644 --- a/cl-quil.asd +++ b/cl-quil.asd @@ -80,7 +80,6 @@ (:file "state-prep") (:file "translators") (:file "modifiers") - (:file "pauli-pair") (:file "linear-paulis") ;; attic'd files / pedagogical purposes only (:file "optimal-2q") From 9cd4327675ee0502125bf367125c6ce7be66836f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 9 Dec 2019 11:33:46 -0800 Subject: [PATCH 23/35] nix pauli-gate in build-gate.lisp --- src/build-gate.lisp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/build-gate.lisp b/src/build-gate.lisp index 9e724c1df..126fe09dd 100644 --- a/src/build-gate.lisp +++ b/src/build-gate.lisp @@ -90,8 +90,6 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build (apply #'build-gate (repeatedly-fork (named-operator roll-name) (length qubits)) params qubit qubits)) -(defun pauli-gate (operator &rest pauli-)) - ;;; functions for dealing with mixed constant vs delayed-expression types (defun param-binary-op (op arg1 arg2) From 783b70812d32f4b9156c64cc42cc5b1ca1bc695a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 9 Dec 2019 16:17:48 -0800 Subject: [PATCH 24/35] respond to PR feedback --- src/ast.lisp | 27 ++++--- src/compilers/linear-paulis.lisp | 121 +++++++++++++++++-------------- src/gates.lisp | 16 ++-- src/matrix-operations.lisp | 13 ++-- src/parser.lisp | 56 +++++++------- 5 files changed, 121 insertions(+), 112 deletions(-) diff --git a/src/ast.lisp b/src/ast.lisp index 4e94e7409..11e27ec2f 100644 --- a/src/ast.lisp +++ b/src/ast.lisp @@ -386,20 +386,25 @@ If no exit rewiring is found, return NIL." :reader permutation-gate-definition-permutation)) (:documentation "A gate definition whose entries can be represented by a permutation of natural numbers.")) -(defclass pauli-sum-gate-definition (gate-definition) +(defclass exp-pauli-sum-gate-definition (gate-definition) ((terms :initarg :terms - :reader pauli-sum-gate-definition-terms + :reader exp-pauli-sum-gate-definition-terms :documentation "List of PAULI-TERMs comprising the sum.") (parameters :initarg :parameters - :reader pauli-sum-gate-definition-parameters + :reader exp-pauli-sum-gate-definition-parameters :documentation "Ordered list of parameter names to be supplied to the definition, which can appear in arithmetical expressions weighting the definition's Pauli terms.") (arguments :initarg :arguments - :reader pauli-sum-gate-definition-arguments + :reader exp-pauli-sum-gate-definition-arguments :documentation "Ordered list of formal arguments appearing in the definition's Pauli terms.")) (:documentation "Represents a gate definition as the exponential of a weighted sum of Pauli matrices.")) -(defmethod gate-definition-qubits-needed ((gate pauli-sum-gate-definition)) - (length (pauli-sum-gate-definition-arguments gate))) +(defstruct (pauli-term) + pauli-word + prefactor + arguments) + +(defmethod gate-definition-qubits-needed ((gate exp-pauli-sum-gate-definition)) + (length (exp-pauli-sum-gate-definition-arguments gate))) (defmethod gate-definition-qubits-needed ((gate permutation-gate-definition)) (ilog2 (length (permutation-gate-definition-permutation gate)))) @@ -1545,18 +1550,18 @@ For example, :stream stream :prefix " ")) - (:method ((gate pauli-sum-gate-definition) (stream stream)) + (:method ((gate exp-pauli-sum-gate-definition) (stream stream)) (format stream "DEFGATE ~A~@[(~{%~A~^, ~})~]~{ ~A~} AS PAULI-SUM:~%" (gate-definition-name gate) - (mapcar #'string (pauli-sum-gate-definition-parameters gate)) - (mapcar #'formal-name (pauli-sum-gate-definition-arguments gate))) - (dolist (pauli-term (pauli-sum-gate-definition-terms gate)) + (mapcar #'string (exp-pauli-sum-gate-definition-parameters gate)) + (mapcar #'formal-name (exp-pauli-sum-gate-definition-arguments gate))) + (dolist (pauli-term (exp-pauli-sum-gate-definition-terms gate)) (with-slots (pauli-word prefactor arguments) pauli-term (format stream " ~a(" pauli-word) (typecase prefactor (number (format stream "~a" prefactor)) - (cons + ((or symbol cons) (print-instruction (make-delayed-expression nil nil prefactor) stream))) (format stream ")") (dolist (arg arguments) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index 2644f6e9e..775e0234c 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -4,7 +4,7 @@ ;;;; ;;;; This file contains routines for the parametric compilation of gates defined ;;;; by time-independent Hamiltonians via expression as a -;;;; PAULI-SUM-GATE. +;;;; EXP-PAULI-SUM-GATE. (in-package #:cl-quil) @@ -35,14 +35,14 @@ (define-compiler parametric-diagonal-compiler ((instr _ - :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) + :where (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) (every (lambda (term) (every (lambda (letter) (or (eql letter #\Z) (eql letter #\I))) (pauli-term-pauli-word term))) - (pauli-sum-gate-terms (gate-application-gate instr)))))) + (exp-pauli-sum-gate-terms (gate-application-gate instr)))))) "Decomposes a diagonal Pauli gate by a single step." (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) (let ((nonlocal-terms nil)) - ;; first, deal with the words with a zero Zs / one Z: they're local gates + ;; first, deal with the words with zero Zs / one Z: they're local gates (dolist (term terms) (multiple-value-bind (Z-count Z-position) (loop :for letter :across (pauli-term-pauli-word term) @@ -105,7 +105,7 @@ (push term Is))))) ;; emit the Is (unless (endp Is) - (let ((I-gate (make-instance 'pauli-sum-gate + (let ((I-gate (make-instance 'exp-pauli-sum-gate :arguments arguments :parameters parameters :arity arity @@ -116,68 +116,77 @@ (application-parameters instr) (application-arguments instr)))) ;; emit the Zs - (labels - ((collect-by-target (term-pairs) - (when (endp term-pairs) - (return-from collect-by-target nil)) - (let ((subvotes (make-array (length arguments)))) - (loop :for (term Z-pos) :in term-pairs - :do (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :for arg :in (pauli-term-arguments term) - :when (and (not (eql j Z-pos)) - (eql #\Z letter)) - :do (incf (aref subvotes (position arg arguments :test #'equalp))))) - (let* ((subvote-position (array-argmax subvotes)) - (subvote-formal (nth subvote-position arguments)) - (subvote-literal (nth subvote-position (application-arguments instr))) - ZZs ZIs) - (dolist (term-pair term-pairs) - (let ((term (car term-pair))) - (cond - ((position subvote-formal (pauli-term-arguments term) :test #'equalp) - (push term-pair ZZs)) - (t - (push term-pair ZIs))))) - (let* ((ZZ-terms - (loop :for (term Z-pos) :in ZZs - :collect (make-pauli-term - :prefactor (pauli-term-prefactor term) - :arguments (pauli-term-arguments term) - :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :if (eql j Z-pos) - :collect #\I - :else - :collect letter) - 'string)))) - (Z-gate (make-instance 'pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :terms ZZ-terms - :name "ZZ-GATE"))) - (inst "CNOT" () control-qubit subvote-literal) - (inst* Z-gate - (application-parameters instr) - (application-arguments instr)) - (inst "CNOT" () control-qubit subvote-literal)) - (collect-by-target ZIs))))) - (collect-by-target Zs))))))) + (let ((subvotes (make-array (length arguments)))) + (loop :for (term Z-pos) :in Zs + :do (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :for arg :in (pauli-term-arguments term) + :when (and (not (eql j Z-pos)) + (eql #\Z letter)) + :do (incf (aref subvotes (position arg arguments :test #'equalp))))) + (let* ((subvote-position (array-argmax subvotes)) + (subvote-formal (nth subvote-position arguments)) + (subvote-literal (nth subvote-position (application-arguments instr))) + ZZs ZIs) + (dolist (term-pair Zs) + (let ((term (car term-pair))) + (cond + ((position subvote-formal (pauli-term-arguments term) :test #'equalp) + (push term-pair ZZs)) + (t + (push term-pair ZIs))))) + (let* ((ZZ-terms + (loop :for (term Z-pos) :in ZZs + :collect (make-pauli-term + :prefactor (pauli-term-prefactor term) + :arguments (pauli-term-arguments term) + :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :if (eql j Z-pos) + :collect #\I + :else + :collect letter) + 'string)))) + (ZZ-gate (make-instance 'exp-pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :terms ZZ-terms + :name "ZZ-GATE")) + (ZI-gate (make-instance 'exp-pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :terms (mapcar #'first ZIs) + :name "ZI-GATE"))) + (unless (zerop (length ZZ-terms)) + (inst "CNOT" () control-qubit subvote-literal) + (inst* ZZ-gate + (application-parameters instr) + (application-arguments instr)) + (inst "CNOT" () control-qubit subvote-literal)) + (unless (zerop (length ZIs)) + (inst* ZI-gate + (application-parameters instr) + (application-arguments instr))))))))))) ;; TODO: also write an orthogonal gate compiler somewhere? approx.lisp will take ;; care of it in the 2Q case, at least. (define-compiler parametric-pauli-compiler - ((instr _ :where (and (typep (gate-application-gate instr) 'pauli-sum-gate) + ((instr _ :where (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) (= 1 (length (application-parameters instr))) (not (typep (first (application-parameters instr)) 'constant))))) "Decomposes a gate described by the exponential of a time-independent Hamiltonian into static orthogonal and parametric diagonal components." (let ((gate (gate-application-gate instr))) (with-slots (arguments parameters terms dimension) gate + ;; make sure that all the pauli terms are all scalar multiples of the unknown parameter. + ;; we track this by making sure that the unknown parameter appears only once and that + ;; the surrounding expression takes a particularly nice form. (labels ((crawl-parameter (p) (typecase p (list @@ -239,7 +248,7 @@ :pauli-word (coerce (make-array (length term-arguments) :initial-element #\Z) 'string)))) - diagonal-gate (make-instance 'pauli-sum-gate + diagonal-gate (make-instance 'exp-pauli-sum-gate :arguments arguments :parameters parameters :terms terms diff --git a/src/gates.lisp b/src/gates.lisp index 7f01a42a3..b64796c64 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -190,16 +190,16 @@ (defmethod gate-matrix ((gate parameterized-gate) &rest parameters) (apply (parameterized-gate-matrix-function gate) parameters)) -(defclass pauli-sum-gate (parameterized-gate) +(defclass exp-pauli-sum-gate (parameterized-gate) ((parameters :initarg :parameters - :reader pauli-sum-gate-parameters) + :reader exp-pauli-sum-gate-parameters) (arguments :initarg :arguments - :reader pauli-sum-gate-arguments) + :reader exp-pauli-sum-gate-arguments) (terms :initarg :terms - :reader pauli-sum-gate-terms)) + :reader exp-pauli-sum-gate-terms)) (:documentation "A gate specified by a Pauli sum.")) -(defmethod initialize-instance :after ((gate pauli-sum-gate) &key) +(defmethod initialize-instance :after ((gate exp-pauli-sum-gate) &key) (with-slots (parameters arguments terms) gate (let ((size (expt 2 (length arguments)))) (flet ((matrix-function (&rest params) @@ -208,7 +208,7 @@ (m+ m (pauli-term->matrix term arguments params parameters))) terms :initial-value (magicl:make-zero-matrix size size)) - (complex 0d0 -1d0) + #C(0d0 -1d0) :hermitian? t))) (setf (%parameterized-gate-matrix-function gate) #'matrix-function))))) @@ -380,9 +380,9 @@ :arity (length params) :matrix-function (compile nil (lambda-form params dim entries)))))) -(defmethod gate-definition-to-gate ((gate-def pauli-sum-gate-definition)) +(defmethod gate-definition-to-gate ((gate-def exp-pauli-sum-gate-definition)) (with-slots (arguments parameters terms) gate-def - (make-instance 'pauli-sum-gate + (make-instance 'exp-pauli-sum-gate :arguments arguments :parameters parameters :terms terms diff --git a/src/matrix-operations.lisp b/src/matrix-operations.lisp index bb3b21467..d55aca8d6 100644 --- a/src/matrix-operations.lisp +++ b/src/matrix-operations.lisp @@ -396,12 +396,13 @@ as needed so that they are the same size." "Computes EXP(M*S). Only works for unitarily diagonalizable matrices M." (multiple-value-bind (d u) (if hermitian? (magicl:hermitian-eig m) (magicl:eig m)) - (assert (matrix-equality m - (m* u - (magicl:diag (length d) (length d) d) - (magicl:conjugate-transpose u))) - () - "MATRIX-EXPT failed to diagonalize its input.") + (when *compress-carefully* + (assert (matrix-equality m + (m* u + (magicl:diag (length d) (length d) d) + (magicl:conjugate-transpose u))) + () + "MATRIX-EXPT failed to diagonalize its input.")) (let* ((size (length d)) (dd (magicl:diag size size (mapcar (lambda (z) (exp (* z s))) d)))) diff --git a/src/parser.lisp b/src/parser.lisp index 19ae6de9f..590eab6fc 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -844,7 +844,6 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (defun parse-gate-definition (tok-lines) "Parse a gate definition from the token lines TOK-LINES." - (declare (optimize (debug 3) (speed 0) (space 0))) ;; Check that we have tokens left (when (null tok-lines) (quil-parse-error "EOF reached when gate definition expected")) @@ -872,30 +871,30 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (multiple-value-bind (params rest-line) (parse-parameters params-args) (multiple-value-bind (args rest-line) (parse-arguments rest-line) - (when (eql ':AS (token-type (first rest-line))) - (pop rest-line) - (let* ((parsed-gate-tok (first rest-line)) - (parsed-gate-type (token-type parsed-gate-tok))) - (unless (find parsed-gate-type '(:MATRIX :PERMUTATION :PAULI-SUM)) - (quil-parse-error "Found unexpected gate type: ~A." (token-payload parsed-gate-tok))) - (setf gate-type parsed-gate-type))) - - (ecase gate-type - (:MATRIX - (when args - (quil-parse-error "DEFGATE AS MATRIX cannot carry formal qubit arguments.")) - (parse-gate-entries-as-matrix body-lines params name :lexical-context op)) - (:PERMUTATION - (when args - (quil-parse-error "DEFGATE AS PERMUTATION cannot carry formal qubit arguments.")) - (when params - (quil-parse-error "Permutation gate definitions do not support parameters.")) - (parse-gate-entries-as-permutation body-lines name :lexical-context op)) - (:PAULI-SUM - (parse-gate-definition-body-into-pauli-sum body-lines name - :lexical-context op - :legal-arguments args - :legal-parameters params))))))))) + (when (eql ':AS (token-type (first rest-line))) + (pop rest-line) + (let* ((parsed-gate-tok (first rest-line)) + (parsed-gate-type (token-type parsed-gate-tok))) + (unless (find parsed-gate-type '(:MATRIX :PERMUTATION :PAULI-SUM)) + (quil-parse-error "Found unexpected gate type: ~A." (token-payload parsed-gate-tok))) + (setf gate-type parsed-gate-type))) + + (ecase gate-type + (:MATRIX + (when args + (quil-parse-error "DEFGATE AS MATRIX cannot carry formal qubit arguments.")) + (parse-gate-entries-as-matrix body-lines params name :lexical-context op)) + (:PERMUTATION + (when args + (quil-parse-error "DEFGATE AS PERMUTATION cannot carry formal qubit arguments.")) + (when params + (quil-parse-error "Permutation gate definitions do not support parameters.")) + (parse-gate-entries-as-permutation body-lines name :lexical-context op)) + (:PAULI-SUM + (parse-gate-definition-body-into-pauli-sum body-lines name + :lexical-context op + :legal-arguments args + :legal-parameters params))))))))) (defun parse-gate-definition-body-into-pauli-sum (body-lines name &key lexical-context legal-arguments legal-parameters) ;; is the immediate next line indented? if not, error. @@ -917,7 +916,7 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an ;; if we're done, return the gate definition (and the rest of the lines) (when dedented? (return-from parse-gate-definition-body-into-pauli-sum - (values (make-instance 'pauli-sum-gate-definition + (values (make-instance 'exp-pauli-sum-gate-definition :name name :terms (nreverse parsed-entries) :context lexical-context @@ -934,11 +933,6 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an :legal-arguments legal-arguments) parsed-entries))))))) -(defstruct (pauli-term) - pauli-word - prefactor - arguments) - (defun parse-pauli-sum-line (line &key lexical-context legal-arguments legal-parameters) "Parses a line inside of a DEFGATE ... AS PAULI-SUM body." (declare (ignore lexical-context legal-parameters)) From 5df4c3f7e04f6c0faecc964d48fcc42421cb4cbb Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 10 Dec 2019 15:34:33 -0800 Subject: [PATCH 25/35] PR response --- src/compilers/linear-paulis.lisp | 36 +++++++++++++++++--------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index 775e0234c..f0b7764c2 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -33,6 +33,17 @@ pos j) :finally (return pos))) +(defun term->count-and-last-Z-position (term) + "For TERM a PAULI-TERM, counts the number of Zs and returns the position of the last occuring Z." + (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :with pos := 0 + :with count := 0 + :when (eql #\Z letter) + :do (setf count (1+ count) + pos j) + :finally (return (values count pos)))) + (define-compiler parametric-diagonal-compiler ((instr _ :where (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) @@ -45,14 +56,7 @@ ;; first, deal with the words with zero Zs / one Z: they're local gates (dolist (term terms) (multiple-value-bind (Z-count Z-position) - (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :with pos := 0 - :with count := 0 - :when (eql #\Z letter) - :do (setf count (1+ count) - pos j) - :finally (return (values count pos))) + (term->count-and-last-Z-position term) (case Z-count (0 nil) @@ -90,7 +94,7 @@ control-qubit (nth vote (application-arguments instr))) ;; now we re-sort the nonlocal terms into two buckets: those with a Z in ;; the voted-upon location, and those without - (let* ((Is nil) (Zs nil)) + (let* (Is Zs) (dolist (term nonlocal-terms) (let ((Z-pos (loop :for letter :across (pauli-term-pauli-word term) :for arg :in (pauli-term-arguments term) @@ -98,13 +102,11 @@ :when (and (eql #\Z letter) (eql vote (position arg arguments :test #'equalp))) :do (return j)))) - (cond - (Z-pos - (push (list term Z-pos) Zs)) - (t - (push term Is))))) + (if Z-pos + (push (list term Z-pos) Zs) + (push term Is)))) ;; emit the Is - (unless (endp Is) + (when Is (let ((I-gate (make-instance 'exp-pauli-sum-gate :arguments arguments :parameters parameters @@ -161,13 +163,13 @@ :dimension dimension :terms (mapcar #'first ZIs) :name "ZI-GATE"))) - (unless (zerop (length ZZ-terms)) + (when ZZ-terms (inst "CNOT" () control-qubit subvote-literal) (inst* ZZ-gate (application-parameters instr) (application-arguments instr)) (inst "CNOT" () control-qubit subvote-literal)) - (unless (zerop (length ZIs)) + (when ZIs (inst* ZI-gate (application-parameters instr) (application-arguments instr))))))))))) From 78ef2ad0cdf521813a201cfc9ff1823b4f81ab34 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 11 Dec 2019 14:06:32 -0800 Subject: [PATCH 26/35] improve readability of parametric-diagonal-compiler --- src/compilers/linear-paulis.lisp | 208 +++++++++++++++---------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index f0b7764c2..669898972 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -44,6 +44,27 @@ pos j) :finally (return (values count pos)))) +(defun clone-exp-pauli-sum-gate (gate &key new-name new-terms) + (with-slots (arguments parameters arity dimension name terms) gate + (make-instance 'exp-pauli-sum-gate + :arguments arguments + :parameters parameters + :arity arity + :dimension dimension + :name (or new-name name) + :terms (or new-terms terms)))) + +(defun find-most-occurrant-Z (terms arguments &key except) + (let ((votes (make-array (length arguments) :initial-element 0))) + (dolist (term terms) + (loop :for letter :across (pauli-term-pauli-word term) + :for argument :in (pauli-term-arguments term) + :when (eql #\Z letter) + :do (incf (aref votes (position argument arguments :test #'equalp))))) + (when except + (setf (aref votes except) 0)) + (array-argmax votes))) + (define-compiler parametric-diagonal-compiler ((instr _ :where (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) @@ -53,48 +74,43 @@ "Decomposes a diagonal Pauli gate by a single step." (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) (let ((nonlocal-terms nil)) - ;; first, deal with the words with zero Zs / one Z: they're local gates - (dolist (term terms) - (multiple-value-bind (Z-count Z-position) - (term->count-and-last-Z-position term) - (case Z-count - (0 - nil) - (1 - (inst "RZ" - (list (param-* 2.0d0 - (tree-substitute (pauli-term-prefactor term) - (mapcar (lambda (ep ap) - (typecase ap - (delayed-expression - (cons ep (delayed-expression-expression ap))) - (otherwise - (cons ep ap)))) - parameters (application-parameters instr))))) - (nth (position (nth Z-position (pauli-term-arguments term)) - arguments :test #'equalp) - (application-arguments instr)))) - (otherwise - (push term nonlocal-terms))))) - (let ((votes (make-array (length arguments) :initial-element 0)) - vote - control-qubit) - ;; we can break nonlocal terms into two collections: those with Zs in - ;; some spot and those without Zs in that spot. the result will be to - ;; conjugate the first collection by CNOT, which flips those Zs to Is. - ;; since we can only emit local gates, almost all such Zs will have to - ;; be eliminated, and so we'll want to pick the position so that this - ;; group is as large as possible. - (dolist (term nonlocal-terms) - (loop :for letter :across (pauli-term-pauli-word term) - :for argument :in (pauli-term-arguments term) - :when (eql #\Z letter) - :do (incf (aref votes (position argument arguments :test #'equalp))))) - (setf vote (array-argmax votes) - control-qubit (nth vote (application-arguments instr))) + ;; first, deal with the words with zero Zs / one Z: they're local gates. + ;; we'll want to evaluate the gate definition at whatever the gate application says. + (let ((substitution-table + (mapcar (lambda (ep ap) + (typecase ap + (delayed-expression + (cons ep (delayed-expression-expression ap))) + (otherwise + (cons ep ap)))) + parameters (application-parameters instr)))) + (dolist (term terms) + (multiple-value-bind (Z-count Z-position) + (term->count-and-last-Z-position term) + (case Z-count + (0 + nil) + (1 + (inst "RZ" + (list (param-* 2.0d0 + (tree-substitute (pauli-term-prefactor term) + substitution-table))) + (nth (position (nth Z-position (pauli-term-arguments term)) + arguments :test #'equalp) + (application-arguments instr)))) + (otherwise + (push term nonlocal-terms)))))) + ;; we can break nonlocal terms into two collections: those with Zs in + ;; some spot and those without Zs in that spot. the result will be to + ;; conjugate the first collection by CNOT, which flips those Zs to Is. + ;; since we can only emit local gates, almost all such Zs will have to + ;; be eliminated, and so we'll want to pick the position so that this + ;; group is as large as possible. + (let* ((vote (find-most-occurrant-Z nonlocal-terms arguments)) + (control-qubit (nth vote (application-arguments instr)))) ;; now we re-sort the nonlocal terms into two buckets: those with a Z in ;; the voted-upon location, and those without - (let* (Is Zs) + (let (Is Zs) (dolist (term nonlocal-terms) (let ((Z-pos (loop :for letter :across (pauli-term-pauli-word term) :for arg :in (pauli-term-arguments term) @@ -105,74 +121,58 @@ (if Z-pos (push (list term Z-pos) Zs) (push term Is)))) - ;; emit the Is + ;; emit the Is as-is (when Is - (let ((I-gate (make-instance 'exp-pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :name "Is" - :terms Is))) + (let ((I-gate (clone-exp-pauli-sum-gate (gate-application-gate instr) + :new-name "Is" :new-terms Is))) (inst* I-gate (application-parameters instr) (application-arguments instr)))) - ;; emit the Zs - (let ((subvotes (make-array (length arguments)))) - (loop :for (term Z-pos) :in Zs - :do (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :for arg :in (pauli-term-arguments term) - :when (and (not (eql j Z-pos)) - (eql #\Z letter)) - :do (incf (aref subvotes (position arg arguments :test #'equalp))))) - (let* ((subvote-position (array-argmax subvotes)) - (subvote-formal (nth subvote-position arguments)) - (subvote-literal (nth subvote-position (application-arguments instr))) - ZZs ZIs) - (dolist (term-pair Zs) - (let ((term (car term-pair))) - (cond - ((position subvote-formal (pauli-term-arguments term) :test #'equalp) - (push term-pair ZZs)) - (t - (push term-pair ZIs))))) - (let* ((ZZ-terms - (loop :for (term Z-pos) :in ZZs - :collect (make-pauli-term - :prefactor (pauli-term-prefactor term) - :arguments (pauli-term-arguments term) - :pauli-word (coerce (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :if (eql j Z-pos) - :collect #\I - :else - :collect letter) - 'string)))) - (ZZ-gate (make-instance 'exp-pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :terms ZZ-terms - :name "ZZ-GATE")) - (ZI-gate (make-instance 'exp-pauli-sum-gate - :arguments arguments - :parameters parameters - :arity arity - :dimension dimension - :terms (mapcar #'first ZIs) - :name "ZI-GATE"))) - (when ZZ-terms - (inst "CNOT" () control-qubit subvote-literal) - (inst* ZZ-gate - (application-parameters instr) - (application-arguments instr)) - (inst "CNOT" () control-qubit subvote-literal)) - (when ZIs - (inst* ZI-gate - (application-parameters instr) - (application-arguments instr))))))))))) + ;; emit the Zs by reducing their Z-counts (for some of them). + ;; we reduce the Z-count by using the identity CNOT ZI CNOT = ZZ, so we seek + ;; a second qubit index with a lot of Zs to conjugate away all at once. + (let* ((subvote-position (find-most-occurrant-Z (mapcar #'car Zs) arguments + :except vote)) + (subvote-formal (nth subvote-position arguments)) + (subvote-literal (nth subvote-position (application-arguments instr))) + ZZs ZIs) + ;; cleave the Zs into ZZs and ZIs. + (dolist (term-pair Zs) + (let ((term (car term-pair))) + (if (position subvote-formal (pauli-term-arguments term) :test #'equalp) + (push term-pair ZZs) + (push term-pair ZIs)))) + ;; for the ZIs: emit them as-is. + (let ((ZI-gate (clone-exp-pauli-sum-gate (gate-application-gate instr) + :new-name "ZI-GATE" :new-terms ZIs))) + (when ZIs + (inst* ZI-gate + (application-parameters instr) + (application-arguments instr)))) + ;; for the ZZs: rewrite them as if they would be ZIs ... + (let* ((ZZs->ZIs + (mapcar (lambda (pair) + (destructuring-bind (term Z-pos) pair + (let ((new-term (copy-pauli-term term))) + (setf (pauli-term-pauli-word new-term) + (coerce (loop :for letter :across (pauli-term-pauli-word term) + :for j :from 0 + :if (eql j Z-pos) + :collect #\I + :else + :collect letter) + 'string)) + new-term))) + ZZs)) + (ZZ-gate (clone-exp-pauli-sum-gate (gate-application-gate instr) + :new-name "ZZ-GATE" :new-terms ZZs->ZIs))) + ;; ... then emit them in a CNOT sandwich. + (when ZZs + (inst "CNOT" () control-qubit subvote-literal) + (inst* ZZ-gate + (application-parameters instr) + (application-arguments instr)) + (inst "CNOT" () control-qubit subvote-literal))))))))) ;; TODO: also write an orthogonal gate compiler somewhere? approx.lisp will take From 3fdca2ca41881e90f4d900aeaaaaa53b7f530c9a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 11 Dec 2019 17:12:39 -0800 Subject: [PATCH 27/35] denote RECOGNIZE-UCR on the global compiler registry --- src/chip/chip-specification.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chip/chip-specification.lisp b/src/chip/chip-specification.lisp index 79e07253f..30ee94ecc 100644 --- a/src/chip/chip-specification.lisp +++ b/src/chip/chip-specification.lisp @@ -392,9 +392,9 @@ used to specify CHIP-SPEC." (constantly 'state-prep-2q-compiler) (constantly 'state-prep-4q-compiler) (constantly 'state-prep-trampolining-compiler) - (constantly 'recognize-ucr) (constantly 'parametric-pauli-compiler) (constantly 'parametric-diagonal-compiler) + (constantly 'recognize-ucr) (constantly 'nearest-circuit-of-depth-0) (lambda (chip-spec arch) (declare (ignore chip-spec)) From 65c85d482f9bfaea3ef689b9b6ca237335a43fcd Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 12:07:49 -0500 Subject: [PATCH 28/35] respond to PR feedback --- cl-quil.asd | 2 +- src/compilers/linear-paulis.lisp | 116 ++++++++++++++----------------- src/parser.lisp | 3 + src/utilities.lisp | 11 +++ 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/cl-quil.asd b/cl-quil.asd index 8ef2ba533..ef7bf63de 100644 --- a/cl-quil.asd +++ b/cl-quil.asd @@ -82,7 +82,7 @@ (:file "modifiers") (:file "linear-paulis") ;; attic'd files / pedagogical purposes only - (:file "optimal-2q") + (:static-file "optimal-2q") (:static-file "cs-compile"))) (:module "analysis" :serial t diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index 669898972..b0817d277 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -23,26 +23,10 @@ (t big-tree))) -(defun array-argmax (arr) - (loop :with pos := 0 - :with current-max := (aref arr 0) - :for j :from 0 - :for item :across arr - :when (< current-max item) - :do (setf current-max item - pos j) - :finally (return pos))) - (defun term->count-and-last-Z-position (term) "For TERM a PAULI-TERM, counts the number of Zs and returns the position of the last occuring Z." - (loop :for letter :across (pauli-term-pauli-word term) - :for j :from 0 - :with pos := 0 - :with count := 0 - :when (eql #\Z letter) - :do (setf count (1+ count) - pos j) - :finally (return (values count pos)))) + (values (count #\Z (pauli-term-pauli-word term)) + (position #\Z (pauli-term-pauli-word term)))) (defun clone-exp-pauli-sum-gate (gate &key new-name new-terms) (with-slots (arguments parameters arity dimension name terms) gate @@ -54,7 +38,7 @@ :name (or new-name name) :terms (or new-terms terms)))) -(defun find-most-occurrant-Z (terms arguments &key except) +(defun find-most-frequent-Z (terms arguments &key except) (let ((votes (make-array (length arguments) :initial-element 0))) (dolist (term terms) (loop :for letter :across (pauli-term-pauli-word term) @@ -63,14 +47,17 @@ :do (incf (aref votes (position argument arguments :test #'equalp))))) (when except (setf (aref votes except) 0)) - (array-argmax votes))) + (vector-argmax votes))) + +(defun pauli-instr-of-all-Zs-p (instr) + "Predicate: INSTR is a GATE-APPLICATION defined by a diagonal EXP-PAULI-SUM-GATE." + (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) + (every (lambda (term) (every (lambda (letter) (or (eql letter #\Z) (eql letter #\I))) + (pauli-term-pauli-word term))) + (exp-pauli-sum-gate-terms (gate-application-gate instr))))) (define-compiler parametric-diagonal-compiler - ((instr _ - :where (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) - (every (lambda (term) (every (lambda (letter) (or (eql letter #\Z) (eql letter #\I))) - (pauli-term-pauli-word term))) - (exp-pauli-sum-gate-terms (gate-application-gate instr)))))) + ((instr _ :where (pauli-instr-of-all-Zs-p instr))) "Decomposes a diagonal Pauli gate by a single step." (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) (let ((nonlocal-terms nil)) @@ -106,7 +93,7 @@ ;; since we can only emit local gates, almost all such Zs will have to ;; be eliminated, and so we'll want to pick the position so that this ;; group is as large as possible. - (let* ((vote (find-most-occurrant-Z nonlocal-terms arguments)) + (let* ((vote (find-most-frequent-Z nonlocal-terms arguments)) (control-qubit (nth vote (application-arguments instr)))) ;; now we re-sort the nonlocal terms into two buckets: those with a Z in ;; the voted-upon location, and those without @@ -131,7 +118,7 @@ ;; emit the Zs by reducing their Z-counts (for some of them). ;; we reduce the Z-count by using the identity CNOT ZI CNOT = ZZ, so we seek ;; a second qubit index with a lot of Zs to conjugate away all at once. - (let* ((subvote-position (find-most-occurrant-Z (mapcar #'car Zs) arguments + (let* ((subvote-position (find-most-frequent-Z (mapcar #'car Zs) arguments :except vote)) (subvote-formal (nth subvote-position arguments)) (subvote-literal (nth subvote-position (application-arguments instr))) @@ -223,41 +210,40 @@ (setf H (m+ H (pauli-term->matrix term arguments (list 1d0) parameters)))) ;; orthogonally diagonalize it: H = O D O^T (multiple-value-bind (diagonal O) (magicl:hermitian-eig H) - (let ((O (orthonormalize-matrix O))) - ;; convert diagonal into a sum of Z paulis - (let ((pauli-prefactors (make-array dimension :initial-element 0d0)) - terms diagonal-gate) - (loop :for d :in diagonal - :for i :from 0 - :do (dotimes (j dimension) - (incf (aref pauli-prefactors j) - (if (evenp (logcount (logand i j))) - (/ d dimension) - (/ (- d) dimension))))) - (setf terms (loop :for prefactor :across pauli-prefactors - :for j :from 0 - :unless (double= 0d0 prefactor) - :collect (let ((term-arguments - (loop :for i :below (length arguments) - :for arg :in arguments - :when (logbitp (- (length arguments) i 1) j) - :collect arg))) - (make-pauli-term - :prefactor (param-* (realpart prefactor) - (make-delayed-expression - nil nil (first parameters))) - :arguments term-arguments - :pauli-word (coerce (make-array (length term-arguments) - :initial-element #\Z) - 'string)))) - diagonal-gate (make-instance 'exp-pauli-sum-gate - :arguments arguments - :parameters parameters - :terms terms - :arity 1 - :dimension dimension - :name (string (gensym "DIAG-PAULI-")))) - ;; emit the instructions - (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) - (inst* diagonal-gate (application-parameters instr) (application-arguments instr)) - (inst* "LEFT-O" O (application-arguments instr))))))))) + ;; convert diagonal into a sum of Z paulis + (let ((pauli-prefactors (make-array dimension :initial-element 0d0)) + terms diagonal-gate) + (loop :for d :in diagonal + :for i :from 0 + :do (dotimes (j dimension) + (incf (aref pauli-prefactors j) + (if (evenp (logcount (logand i j))) + (/ d dimension) + (/ (- d) dimension))))) + (setf terms (loop :for prefactor :across pauli-prefactors + :for j :from 0 + :unless (double= 0d0 prefactor) + :collect (let ((term-arguments + (loop :for i :below (length arguments) + :for arg :in arguments + :when (logbitp (- (length arguments) i 1) j) + :collect arg))) + (make-pauli-term + :prefactor (param-* (realpart prefactor) + (make-delayed-expression + nil nil (first parameters))) + :arguments term-arguments + :pauli-word (coerce (make-array (length term-arguments) + :initial-element #\Z) + 'string)))) + diagonal-gate (make-instance 'exp-pauli-sum-gate + :arguments arguments + :parameters parameters + :terms terms + :arity 1 + :dimension dimension + :name (string (gensym "DIAG-PAULI-")))) + ;; emit the instructions + (inst* "RIGHT-O-T" (magicl:conjugate-transpose O) (application-arguments instr)) + (inst* diagonal-gate (application-parameters instr) (application-arguments instr)) + (inst* "LEFT-O" O (application-arguments instr)))))))) diff --git a/src/parser.lisp b/src/parser.lisp index 590eab6fc..afcbc8a40 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -943,6 +943,9 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (unless (eql ':NAME (token-type (first line))) (quil-parse-error "DEFGATE AS PAULI-SUM body line begins with something other than a Pauli word: ~a" (first line))) (setf pauli-word (token-payload (pop line))) + (unless (every (lambda (c) (member c '(#\I #\X #\Y #\Z))) pauli-word) + (quil-parse-error "DEFGATE AS PAULI-SUM body line contains Pauli word with letters other than I, X, Y, Z: ~a" + pauli-word)) ;; LPAREN (unless (eql ':LEFT-PAREN (token-type (first line))) (quil-parse-error "Pauli term requires a parenthesized scalar factor, but found ~a instead of LPAREN" (first line))) diff --git a/src/utilities.lisp b/src/utilities.lisp index a09144685..622e96aec 100644 --- a/src/utilities.lisp +++ b/src/utilities.lisp @@ -39,6 +39,17 @@ WARNING: The default will work for instances of \"idiomatic\" classes that aren' (defun (setf vnth) (val index vector) (setf (aref vector index) val)) +(defun vector-argmax (arr) + "Finds the position of the largest (using #'<) item in a nonempty vector." + (loop :with pos := 0 + :with current-max := (aref arr 0) + :for j :from 0 + :for item :across arr + :when (< current-max item) + :do (setf current-max item + pos j) + :finally (return pos))) + (defmacro dohash (((key val) hash &optional ret) &body body) `(loop :for ,key :being :the :hash-keys :of ,hash :using (hash-value ,val) From aea984ddbbe823e33a3917267eda0f886e8597cb Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 12:20:22 -0500 Subject: [PATCH 29/35] pick nit --- src/compilers/linear-paulis.lisp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index b0817d277..ccd2c05dc 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -49,7 +49,7 @@ (setf (aref votes except) 0)) (vector-argmax votes))) -(defun pauli-instr-of-all-Zs-p (instr) +(defun pauli-instr-of-all-Zs-or-Is-p (instr) "Predicate: INSTR is a GATE-APPLICATION defined by a diagonal EXP-PAULI-SUM-GATE." (and (typep (gate-application-gate instr) 'exp-pauli-sum-gate) (every (lambda (term) (every (lambda (letter) (or (eql letter #\Z) (eql letter #\I))) @@ -57,7 +57,7 @@ (exp-pauli-sum-gate-terms (gate-application-gate instr))))) (define-compiler parametric-diagonal-compiler - ((instr _ :where (pauli-instr-of-all-Zs-p instr))) + ((instr _ :where (pauli-instr-of-all-Zs-or-Is-p instr))) "Decomposes a diagonal Pauli gate by a single step." (with-slots (arguments parameters terms arity dimension) (gate-application-gate instr) (let ((nonlocal-terms nil)) From 6b1a4b48ae2161075632990bec35c879956186d7 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 15:38:27 -0500 Subject: [PATCH 30/35] now pick robert's nits --- src/ast.lisp | 3 +++ src/build-gate.lisp | 6 +++++- src/gates.lisp | 4 +++- src/parser.lisp | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ast.lisp b/src/ast.lisp index 11e27ec2f..e213673b5 100644 --- a/src/ast.lisp +++ b/src/ast.lisp @@ -399,6 +399,9 @@ If no exit rewiring is found, return NIL." (:documentation "Represents a gate definition as the exponential of a weighted sum of Pauli matrices.")) (defstruct (pauli-term) + "Records a word of Pauli operators, together with an ordered list of qubit formals on which they act, as well as a scalar prefix. This is part of the internal representation of a EXP-PAULI-SUM-GATE-DEFINITION and probably shouldn't be instantiated otherwise. + +This replicates some of the behavior of CL-QUIL.CLIFFORD::PAULI, but it extends it slightly: a Clifford Pauli is constrained to carry a phase which is a fourth root of unity, but the phase of a PAULI-TERM can be arbitrary (indeed, even a delayed expression). The reader looking for an embodiment of Pauli words is better off using that data structure without CAREFUL CONSIDERATION." pauli-word prefactor arguments) diff --git a/src/build-gate.lisp b/src/build-gate.lisp index 126fe09dd..174b34fc3 100644 --- a/src/build-gate.lisp +++ b/src/build-gate.lisp @@ -55,7 +55,11 @@ EXAMPLE: The Quil line \"CPHASE(pi) 2 3\" corresponds to the S-expression (build (define-global-counter **anonymous-gate-counter** get-anonymous-gate-counter) (defun anon-gate (operator-or-gate gate-or-parameters qubit &rest qubits) - "Variant of BUILD-GATE for constructing anonymous gate applications." + "Variant of BUILD-GATE for constructing anonymous gate applications. + +Comes in two flavors: + (1) (anon-gate string-name magicl-matrix &rest qubits) builds an anonymous gate application with human-readable name STRING-NAME, behavior indicated by MAGICL-MATRIX, and acting on QUBITS. + (2) (anon-gate gate-definition parameter-list &rest qubits) builds an anonymous gate application with definition set by GATE-DEFINITION, human-readable name inferred from the defintiion, PARAMETER-LIST fed to the definition, and acting on QUBITS." (push qubit qubits) (let* ((name (etypecase operator-or-gate diff --git a/src/gates.lisp b/src/gates.lisp index b64796c64..d158bfaa0 100644 --- a/src/gates.lisp +++ b/src/gates.lisp @@ -197,7 +197,9 @@ :reader exp-pauli-sum-gate-arguments) (terms :initarg :terms :reader exp-pauli-sum-gate-terms)) - (:documentation "A gate specified by a Pauli sum.")) + (:documentation "A gate specified by the exponentiation of a weighted sum of Paulis. + +The Pauli sum is recorded as a list of PAULI-TERM objects, stored in the TERMS slot, each of which is made up of a phase factor, a string of Pauli symbols (I, X, Y, Z), and an ordered list of qubit formals to which these symbols are applied. The qubit formals are those appearing in the ARGUMENTS slot, which ultimately get substituted with the arguments appearing in a GATE-APPLICATION tagged with this gate definition. Similarly, PARAMETERS is populated with a list of formals on which the Pauli phases can depend, and these are ultimately substituted with the parameters appearing in a GATE-APPLICATION tagged with this gate definition.")) (defmethod initialize-instance :after ((gate exp-pauli-sum-gate) &key) (with-slots (parameters arguments terms) gate diff --git a/src/parser.lisp b/src/parser.lisp index afcbc8a40..ea405f383 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -1008,7 +1008,7 @@ If ENSURE-VALID is T, then a memory reference such as 'foo[0]' will result in an (setf entry (- entry))) (#\Y (setf row (dpb (- 1 row-toggle) (byte 1 arg-position) row)) - (setf entry (* entry (if (zerop row-toggle) (complex 0 1) (complex 0 -1))))) + (setf entry (* entry (if (zerop row-toggle) #C(0 1) #C(0 -1))))) (#\Z (setf entry (* entry (if (zerop row-toggle) 1 -1)))) (#\I From 0289c7f3883f7fe65f49429dddf8fbdb336bb822 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 16:37:46 -0500 Subject: [PATCH 31/35] avoid unnecessary p-p-c calls --- src/chip/chip-specification.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chip/chip-specification.lisp b/src/chip/chip-specification.lisp index 30ee94ecc..d51c715cd 100644 --- a/src/chip/chip-specification.lisp +++ b/src/chip/chip-specification.lisp @@ -392,8 +392,8 @@ used to specify CHIP-SPEC." (constantly 'state-prep-2q-compiler) (constantly 'state-prep-4q-compiler) (constantly 'state-prep-trampolining-compiler) - (constantly 'parametric-pauli-compiler) (constantly 'parametric-diagonal-compiler) + (constantly 'parametric-pauli-compiler) (constantly 'recognize-ucr) (constantly 'nearest-circuit-of-depth-0) (lambda (chip-spec arch) From de27a6e366a2a2d1d05e17f73081a837affaffa7 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 16:38:20 -0500 Subject: [PATCH 32/35] skip emitting needless gates in p-d-c --- src/compilers/linear-paulis.lisp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index ccd2c05dc..2ff75f034 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -115,6 +115,8 @@ (inst* I-gate (application-parameters instr) (application-arguments instr)))) + (unless Zs + (finish-compiler)) ;; emit the Zs by reducing their Z-counts (for some of them). ;; we reduce the Z-count by using the identity CNOT ZI CNOT = ZZ, so we seek ;; a second qubit index with a lot of Zs to conjugate away all at once. From eb67698acc5985dc654cdff3bf14c3229c337c56 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 16:38:36 -0500 Subject: [PATCH 33/35] fix bug in emitting ZI gate --- src/compilers/linear-paulis.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compilers/linear-paulis.lisp b/src/compilers/linear-paulis.lisp index 2ff75f034..1af9f08de 100644 --- a/src/compilers/linear-paulis.lisp +++ b/src/compilers/linear-paulis.lisp @@ -130,7 +130,7 @@ (let ((term (car term-pair))) (if (position subvote-formal (pauli-term-arguments term) :test #'equalp) (push term-pair ZZs) - (push term-pair ZIs)))) + (push term ZIs)))) ;; for the ZIs: emit them as-is. (let ((ZI-gate (clone-exp-pauli-sum-gate (gate-application-gate instr) :new-name "ZI-GATE" :new-terms ZIs))) From c9f80dee72a2baa814e6a1144e60555be3745259 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 17 Dec 2019 16:38:47 -0500 Subject: [PATCH 34/35] add test for parametric pauli compiler --- tests/translator-tests.lisp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/translator-tests.lisp b/tests/translator-tests.lisp index 65f463315..3d3503b3d 100644 --- a/tests/translator-tests.lisp +++ b/tests/translator-tests.lisp @@ -222,6 +222,43 @@ (format t "~& Tested ~2,2F% of all compilers." (* 100 hit-rate)) (is (< 1/10 hit-rate)))))) +(defun random-pauli-word (n) + (coerce (loop :for j :below n + :collect (case (random 4) + (0 #\X) + (1 #\Y) + (2 #\Z) + (3 #\I))) + 'string)) + +(deftest test-parametric-defexpi () + (let* ((qubit-count 3) + (string-count 6) + (pauli-strings (loop :for n :below string-count + :collect (format nil " ~a(~f*%t)~{ q~a~}" + (random-pauli-word qubit-count) (random pi) + (a:iota qubit-count)))) + (program (format nil " +PRAGMA INITIAL_REWIRING \"NAIVE\" +DECLARE time REAL +DEFGATE U(%t)~{ q~a~} AS PAULI-SUM: +~{~a~%~} + +U(time)~{ ~a~}" + (a:iota qubit-count) + pauli-strings + (a:iota qubit-count))) + (patch-table (alexandria:plist-hash-table (list "time" (random 2pi)) + :test #'equalp)) + (pp (parse-quil program)) + (original-output (quil::make-matrix-from-quil + (mapcar (a:rcurry #'%patch-mref-values patch-table) + (coerce (parsed-program-executable-code pp) 'list)))) + (cpp (compiler-hook pp (quil::build-nq-fully-connected-chip 3))) + (compiled-output (quil::make-matrix-from-quil + (mapcar (a:rcurry #'%patch-mref-values patch-table) + (coerce (parsed-program-executable-code cpp) 'list))))) + (is (quil::matrix-equals-dwim original-output compiled-output)))) (defun random-permutation (list) (unless (null list) From f2427c42e00c6402d1e1df4d2f407dc86ea879f4 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 18 Dec 2019 09:49:07 -0500 Subject: [PATCH 35/35] kill stray magic number --- tests/translator-tests.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/translator-tests.lisp b/tests/translator-tests.lisp index 3d3503b3d..233854730 100644 --- a/tests/translator-tests.lisp +++ b/tests/translator-tests.lisp @@ -254,7 +254,7 @@ U(time)~{ ~a~}" (original-output (quil::make-matrix-from-quil (mapcar (a:rcurry #'%patch-mref-values patch-table) (coerce (parsed-program-executable-code pp) 'list)))) - (cpp (compiler-hook pp (quil::build-nq-fully-connected-chip 3))) + (cpp (compiler-hook pp (quil::build-nq-fully-connected-chip qubit-count))) (compiled-output (quil::make-matrix-from-quil (mapcar (a:rcurry #'%patch-mref-values patch-table) (coerce (parsed-program-executable-code cpp) 'list)))))