Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensure applicable symbolic-accepting compilers are included #669

Merged
merged 1 commit into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 119 additions & 10 deletions src/define-compiler.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -353,15 +353,35 @@ What \"concrete\" means depends on the types of A and B:
(:method ((a wildcard-binding) (b gate-binding))
b))

(defvar *instantiate-binding-with-symbolic-parameters* nil
"When calling INSTANTIATE-BINDING and encountering a symbolic parameter, instead of erroring, supply a symbolic parameter.")

(defun symbolic-param-generator ()
(let ((n -1))
(lambda ()
(make-delayed-expression nil nil `(* 1.0 ,(mref "G" (incf n)))))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gross expressions like this one make me feel the AST stuff around delayed expressions should be seriously rethought. There's nothing to do about it here, but someone (perhaps me) should open an issue about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.


(defun instantiate-parameters (params)
;; Instantiate PARAMS with symbolic arguments. Used as a helper
;; below.
(let ((gen (symbolic-param-generator)))
(mapcar (lambda (p)
(if (symbolp p)
(funcall gen)
p))
params)))

(defgeneric instantiate-binding (binding)
(:documentation "When possible, construct the unique instruction on which BINDING will match. If any of the binding's arguments are unspecified (i.e. match against any qubit), those arguments will be filled-in with unique qubit indices.")
(:method (binding)
(error 'cannot-concretize-binding))
(:method ((binding gate-binding))
(when (symbolp (gate-binding-operator binding))
(error 'cannot-concretize-binding))
(when (or (symbolp (gate-binding-parameters binding))
(some #'symbolp (gate-binding-parameters binding)))
(when (symbolp (gate-binding-parameters binding))
(error 'cannot-concretize-binding))
(when (and (some #'symbolp (gate-binding-parameters binding))
(not *instantiate-binding-with-symbolic-parameters*))
(error 'cannot-concretize-binding))
(when (some #'symbolp (gate-binding-arguments binding))
(let* ((usedq (gate-binding-arguments binding))
Expand All @@ -371,7 +391,7 @@ What \"concrete\" means depends on the types of A and B:
usedq)))))
(apply #'build-gate
(gate-binding-operator binding)
(gate-binding-parameters binding)
(instantiate-parameters (gate-binding-parameters binding))
(gate-binding-arguments binding))))


Expand Down Expand Up @@ -405,6 +425,15 @@ What \"concrete\" means depends on the types of A and B:
:initarg :output-gates
:reader compiler-output-gates
:documentation "Information automatically extracted about the target gate set.")
(info-plist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beware: misc-data was not a great idea in chip-specification, and the short-form docs for this sound OK (cached info! sure!) the details are sketchier (in what sense is :SYMBOLIC caching anything?). Are we sure this tag doesn't belong at top-level in this object?

:initform nil
:accessor compiler-info-plist
:documentation "A plist of inessential information. You will likely find cached data here. Data includes:

:SYMBOLIC <boolean>
Accepts symbolic parameters.

N.B. This is *NOT* the same as COMPILER-OPTIONS.")
(%function
:initarg :function
:reader compiler-%function
Expand Down Expand Up @@ -668,6 +697,75 @@ N.B.: The word \"shortest\" here is a bit fuzzy. In practice it typically means
(compiler-bindings x)))
(get-compilers qubit-count)))

(defun compiler-allows-symbolic-parameters-p (compiler)
"Does COMPILER accept instructions with symbolic parameters?

Compilers matching gates which don't have parameters don't count as accepting parameters."
(let ((info (getf (compiler-info-plist compiler) ':SYMBOLIC '#1=#:unknown)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO getf should return a secondary value like gethash does. Where do I file a complaint?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/dev/null

(if (eq info '#1#)
(setf (getf (compiler-info-plist compiler) ':SYMBOLIC)
(heuristically-determine-if-compiler-allows-symbolic-parameters-p compiler))
info)))

(defun heuristically-determine-if-compiler-allows-symbolic-parameters-p (compiler)
;; Things marked as "HEURISTIC ALERT" are not perfect tests, but
;; good enough in practice.
(block nil
(let* ((bindings (compiler-bindings compiler)))
;; All bindings must be gate bindings.
(unless (every (lambda (b) (typep b 'gate-binding)) bindings)
(return nil))
;; Now we know we can unpack the bindings.
;;
;; HEURISTIC ALERT: Before we do that, we'll check we are
;; actually operating on named gates. One could imagine the
;; compilation of symbolic 2Q operators with wildcard 1Q gates
;; surrounding, but we don't have this kind of thing anywhere.
(unless (every (lambda (b) (typep (gate-binding-operator b) 'operator-description )) bindings)
(return nil))
;; Now we descend to look at the bindings' parameter lists for
;; heuristic conformance.
(let ((parameter-lists (map 'list #'gate-binding-parameters bindings)))
;; HEURISTIC ALERT: All bindings have fewer than two
;; parameters. This rule doesn't necessarily mean a compiler
;; doesn't accept symbolic parameters, but it is unlikely in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this also unlikely when using parametric Pauli sums?

;; practice.
;;
;; We could also relax this rule by allowing bindings with
;; any number of constant parameters. A fish to fry another
;; day.
(unless (every (lambda (p) (and (alexandria:proper-list-p p)
(= 1 (length p))))
parameter-lists)
(return nil))
;; Check that we have at least one non-constant parameter.
(unless (some (lambda (p) (symbolp (first p))) parameter-lists)
(return nil))
;; Ok, so, here's what we now know:
;;
;; - All bindings take 1 parameter
;;
;; - At least one binding takes an arbitrary parameter.
(let ((*instantiate-binding-with-symbolic-parameters* t))
(let ((prototype-instructions (map 'list #'instantiate-binding bindings)))
;; We are ready to lock & load.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lock & load the hounds

(handler-bind ((compiler-does-not-apply
(lambda (c)
(declare (ignore c))
(return nil)))
(error
(lambda (c)
(declare (ignore c))
;; Decline to handle, but issue a warning.
(warn "When checking if the compiler ~A accepts ~
symbolic parameters, an unexpected error ~
occurred. This is probably because this ~
compiler matches against inputs it's not ~
actually able to process, like symbolic ~
expressions." (compiler-name compiler)))))
(apply compiler prototype-instructions) ; May return an empty list!
t)))))))

(defun path-has-a-loop-p (path start)
(and (< 1 (length path))
(loop :for (table compiler) :on path :by #'cddr
Expand All @@ -692,9 +790,16 @@ N.B.: The word \"shortest\" here is a bit fuzzy. In practice it typically means
qubit-count))
(generic-cost (occurrence-table-metric-fidelity
(evaluate-occurrence-table (first generic-path) target-gateset))))
;; it may be that non-generic gates have shorter routes to the target gate set.
;; each possible such route begins with a specialized compiler.
;; so, iterate over specialized compilers and see if they lead anywhere nice.
;; it may be that non-generic gates have shorter routes to the
;; target gate set. each possible such route begins with a
;; specialized compiler.
;;
;; it's also possible a specialized compiler actually allows a
;; broader set of gates with symbolic arguments, even if it's
;; higher cost.
;;
;; so, iterate over specialized compilers and see if they lead
;; anywhere nice.
(let ((compiler-hash (make-hash-table)))
(dolist (compiler generic-path)
(when (typep compiler 'compiler)
Expand All @@ -708,10 +813,14 @@ N.B.: The word \"shortest\" here is a bit fuzzy. In practice it typically means
(special-cost (occurrence-table-metric-fidelity
(evaluate-occurrence-table (first special-path) target-gateset))))
;; did we in fact beat out the generic machinery?
(if (and (not (path-has-a-loop-p special-path (compiler-bindings compiler)))
(>= special-cost generic-cost))
;; then store it!
(setf (gethash compiler compiler-hash) special-cost))))
;;
;; or does this special compiler actually allow a broader
;; set of inputs than the generic ones?
(when (or (compiler-allows-symbolic-parameters-p compiler)
(and (not (path-has-a-loop-p special-path (compiler-bindings compiler)))
(>= special-cost generic-cost)))
;; then store it ya dingus!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

(setf (gethash compiler compiler-hash) special-cost))))

;; these are basically all the compilers we care to use; now we need to
;; sort them into preference order.
Expand Down
39 changes: 39 additions & 0 deletions tests/compilation-tests.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,42 @@ CNOT 4 8

(deftest test-free-rx-rz-strings-reduce ()
(%test-reduction-with-chip 3 (%read-test-chipspec "1q-free-rx.qpu")))

(deftest test-symbolic-parameter-compiles-in-examples ()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to split this up into tests for the new symbolic parameter detection and then another for compiler hooks directly from #667?

"There have been bugs where symbolic compilers aren't found because non-symbolic ones are lower cost. (Bug #667)"
;; Check we can compute this fact right.
(is (quil::compiler-allows-symbolic-parameters-p #'quil::RX-to-ZXZXZ))
;; If this happens to change in the future... let me know!
(is (not (quil::compiler-allows-symbolic-parameters-p #'quil::EULER-ZYZ-COMPILER)))
;; Some full stack test cases that arose out of bug reports.
(not-signals error
(quil:compiler-hook (quil:parse-quil "
DECLARE theta REAL
RX(theta) 0")
(quil::build-nq-linear-chip 2)))
(not-signals error
(quil:compiler-hook (quil:parse-quil "
PRAGMA INITIAL_REWIRING \"NAIVE\"
DECLARE ro BIT[5]
DECLARE theta REAL[2]
H 1
H 2
H 3
H 4
H 5
CPHASE(theta[0]) 1 2
CPHASE(theta[0]) 2 3
CPHASE(theta[0]) 3 4
CPHASE(theta[0]) 4 5
RX(theta[1]) 1
RX(theta[1]) 2
RX(theta[1]) 3
RX(theta[1]) 4
RX(theta[1]) 5
MEASURE 1 ro[0]
MEASURE 2 ro[1]
MEASURE 3 ro[2]
MEASURE 4 ro[3]
MEASURE 5 ro[4]
")
(quil::build-nq-linear-chip 6))))