From 4dbf0db5592d567d4af367fbebdf664dfd99e3b0 Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Thu, 9 Dec 2021 11:50:18 -0800 Subject: [PATCH 1/5] Generalize chips specs for higher order objects more thoroughly. There were a couple places in the data structure definitions that hardcoded assumptions about the order of the objects (to <1): * The length of the OBJECTS vector in a CHIP-SPEC. * The length of the CXNS vector in a HARDWARE-OBJECT. * Various constructors and initializers for building complete chip specifications. * The computation of the lookup cache. To solve this problem we introduce more general abstractions that cover the use case of building a chip-specification, as well as making on-demand adjustable vectors for those whose lengths depend on the order of the objects. To make sure this works we have an example chip spec consisting of just 3 qubits and a 3 qubit gate on them, that can do CCNOT. --- benchmarking/package.lisp | 1 - benchmarking/qaoa-tests/generate-program.lisp | 2 +- benchmarking/rewiring-analysis.lisp | 25 +-- boondoggle/src/producers.lisp | 2 +- src/addresser/addresser-common.lisp | 10 +- src/addresser/addresser-state.lisp | 4 +- src/addresser/astar-rewiring-search.lisp | 2 +- src/addresser/fidelity-addresser.lisp | 4 +- src/addresser/temporal-addresser.lisp | 2 +- src/chip/chip-reader.lisp | 13 +- src/chip/chip-specification.lisp | 200 ++++++++++++++---- src/compressor/compressor.lisp | 16 +- 12 files changed, 198 insertions(+), 83 deletions(-) diff --git a/benchmarking/package.lisp b/benchmarking/package.lisp index de0bb6714..b2b5404b9 100644 --- a/benchmarking/package.lisp +++ b/benchmarking/package.lisp @@ -16,7 +16,6 @@ chip-spec-live-qubit-cc chip-specification chip-specification-objects - hardware-object-cxns make-memory-descriptor pragma-end-commuting-blocks pragma-end-block diff --git a/benchmarking/qaoa-tests/generate-program.lisp b/benchmarking/qaoa-tests/generate-program.lisp index fa80e1d3e..eff997c2a 100644 --- a/benchmarking/qaoa-tests/generate-program.lisp +++ b/benchmarking/qaoa-tests/generate-program.lisp @@ -96,7 +96,7 @@ NOTE: This copies the list first, and so is safe to apply to &REST lists." :do (return)) ;; collect all links between these qubits (loop :for link-obj :across (vnth 1 (chip-specification-objects chip-specification)) - :for qubit-list := (first (hardware-object-cxns link-obj)) + :for qubit-list := (first (quil::hardware-object-cxns link-obj)) :when (subsetp qubit-list program-qubits) :do (push qubit-list program-links)) (apply 'qaoa-program-from-graph diff --git a/benchmarking/rewiring-analysis.lisp b/benchmarking/rewiring-analysis.lisp index 25e3db9d2..61628d296 100644 --- a/benchmarking/rewiring-analysis.lisp +++ b/benchmarking/rewiring-analysis.lisp @@ -29,28 +29,21 @@ (defun init-chip (&key (architecture ':cz)) "Initialize a chip from a given architecture with no objects" (let ((chip-spec (quil::make-chip-specification - :objects (vector (quil::make-adjustable-vector) - (quil::make-adjustable-vector)) :generic-rewriting-rules (coerce (quil::global-rewriting-rules) 'vector)))) (quil::install-generic-compilers chip-spec architecture) chip-spec)) +;;; FIXME: Duplicate of BUILD-CHIP-FROM-DIGRAPH (defun make-graph-chip (graph &key (architecture ':cz)) "Make a chip from a graph" - (let* ((chip-spec (init-chip :architecture architecture)) - (qubits - (loop :for i :below (length graph) :collect (quil::build-qubit i :type '(:RZ :X/2 :MEASURE)))) - (qubit-array (make-array (length graph) :initial-contents qubits)) - (links - (loop - :for (a . b) :in (graph-edges graph) - :for link-index :from 0 - :collect (quil::build-link a b :type architecture) - :do (vector-push-extend link-index (quil::vnth 1 (quil::hardware-object-cxns (aref qubit-array a)))) - :do (vector-push-extend link-index (quil::vnth 1 (quil::hardware-object-cxns (aref qubit-array b))))))) - (setf (quil::chip-specification-objects chip-spec) - (make-array 2 :initial-contents (list qubit-array - (coerce links 'vector)))) + (let ((chip-spec (init-chip :architecture architecture))) + (loop :for i :below (length graph) + :do (quil::adjoin-hardware-object + (quil::build-qubit i :type '(:RZ :X/2 :MEASURE)) + chip-spec)) + (loop + :for (a . b) :in (graph-edges graph) + :do (quil::install-link-onto-chip chip-spec a b :architecture architecture)) (quil:warm-hardware-objects chip-spec))) ;; 0 -- 1 diff --git a/boondoggle/src/producers.lisp b/boondoggle/src/producers.lisp index 0d3bd040a..77885c82c 100644 --- a/boondoggle/src/producers.lisp +++ b/boondoggle/src/producers.lisp @@ -120,7 +120,7 @@ PARAMETER-BOUNDS is a list of maximum random values for the gate parameters." "This gate spec expects higher order hardware objects to exist.") (elt (quil::chip-specification-objects chip-specification) gate-order))) (device-index (random (length available-devices)))) - (quil::vnth 0 (quil::hardware-object-cxns (quil::vnth device-index available-devices))))) + (quil::objects-on-hardware-object 0 (quil::vnth device-index available-devices)))) (t (assert (< gate-order qubits-on-device) nil diff --git a/src/addresser/addresser-common.lisp b/src/addresser/addresser-common.lisp index c1cc91746..a7f971161 100644 --- a/src/addresser/addresser-common.lisp +++ b/src/addresser/addresser-common.lisp @@ -469,11 +469,11 @@ If DRY-RUN, this returns T as soon as it finds an instruction it can handle." (loop :for (candidate-hardware-index candidate-instr) :in instrs - :for physical-qubits := (coerce (vnth 0 - (hardware-object-cxns - (chip-spec-hw-object chip-spec (1- (length (application-arguments - candidate-instr))) - candidate-hardware-index))) + :for physical-qubits := (coerce (objects-on-hardware-object + 0 + (chip-spec-hw-object chip-spec (1- (length (application-arguments + candidate-instr))) + candidate-hardware-index)) 'list) :for candidate-cost := (cost-function state :instr candidate-instr) diff --git a/src/addresser/addresser-state.lisp b/src/addresser/addresser-state.lisp index 8324afa6c..3cf51f426 100644 --- a/src/addresser/addresser-state.lisp +++ b/src/addresser/addresser-state.lisp @@ -144,7 +144,7 @@ INSTR is the \"active instruction\". (loop :for link :across (chip-spec-links chip-spec) :for value := (funcall link-cost link) :do (destructuring-bind (q0 q1) - (coerce (vnth 0 (hardware-object-cxns link)) 'list) + (coerce (objects-on-hardware-object 0 link) 'list) (setf (aref dist q0 q1) value (aref dist q1 q0) value))) ;; for each intermediate vertex... @@ -178,7 +178,7 @@ INSTR is the \"active instruction\". (let ((hw (vnth j order-list))) (unless (hardware-object-dead-p hw) (let* ((instr (apply #'anon-gate "FLEX" (random-special-unitary (expt 2 qubits)) - (or (coerce (vnth 0 (hardware-object-cxns hw)) 'list) + (or (coerce (objects-on-hardware-object 0 hw) 'list) (list j)))) (instrs-decomposed (expand-to-native-instructions (list instr) chip-spec)) (instrs-compressed (if *compute-tight-recombination-bound* diff --git a/src/addresser/astar-rewiring-search.lisp b/src/addresser/astar-rewiring-search.lisp index c2ac4a797..fed9b6be4 100644 --- a/src/addresser/astar-rewiring-search.lisp +++ b/src/addresser/astar-rewiring-search.lisp @@ -70,7 +70,7 @@ index at a time, rather than one octet at a time." (defun next-swap-info (swap) (with-slots (link) swap - (destructuring-bind (q0 q1) (coerce (vnth 0 (hardware-object-cxns link)) 'list) + (destructuring-bind (q0 q1) (coerce (objects-on-hardware-object 0 link) 'list) ;; TODO: Eventually this should look up swap in the list. (See cost-function.lisp) (values (permutation-record-duration (vnth 0 (hardware-object-permutation-gates link))) diff --git a/src/addresser/fidelity-addresser.lisp b/src/addresser/fidelity-addresser.lisp index 7e10b93fa..a00e93484 100644 --- a/src/addresser/fidelity-addresser.lisp +++ b/src/addresser/fidelity-addresser.lisp @@ -53,7 +53,7 @@ (subschedule (chip-contiguous-subschedule-from-last-instructions (addresser-state-chip-schedule state) (apply #'make-qubit-resource - (coerce (vnth 0 (hardware-object-cxns hardware-object)) 'list)))) + (coerce (objects-on-hardware-object 0 hardware-object) 'list)))) (preceding-fidelity (calculate-instructions-log-fidelity subschedule (addresser-state-chip-specification state)))) @@ -205,7 +205,7 @@ (swap (apply #'build-gate (permutation-record-operator permutation-record) '() - (coerce (vnth 0 (hardware-object-cxns hardware-object)) 'list)))) + (coerce (objects-on-hardware-object 0 hardware-object) 'list)))) (calculate-instructions-fidelity (expand-to-native-instructions (list swap) chip-spec) chip-spec))) (defmethod initialize-instance :after ((instance fidelity-addresser-state) diff --git a/src/addresser/temporal-addresser.lisp b/src/addresser/temporal-addresser.lisp index ca164e349..e6f98272b 100644 --- a/src/addresser/temporal-addresser.lisp +++ b/src/addresser/temporal-addresser.lisp @@ -102,7 +102,7 @@ (chip-schedule-resource-carving-point (addresser-state-chip-schedule state) (apply #'make-qubit-resource - (coerce (vnth 0 (hardware-object-cxns hardware-object)) 'list)))))) + (coerce (objects-on-hardware-object 0 hardware-object) 'list)))))) (setf time (min time intelligent-bound))) time)))) diff --git a/src/chip/chip-reader.lisp b/src/chip/chip-reader.lisp index ea9cfa5e6..d343a1b9c 100644 --- a/src/chip/chip-reader.lisp +++ b/src/chip/chip-reader.lisp @@ -256,13 +256,12 @@ (t (setf link (build-link q0 q1 :type '(:CZ))))) (when link - ;; notify the qubits that they're attached. - (dolist (qubit-index (list q0 q1)) - (vector-push-extend link-index (chip-spec-links-on-qubit chip-spec qubit-index))) + (adjoin-hardware-object link chip-spec) + ;; set up the connections between the qubits and the links + (connect-hardware-objects chip-spec link (chip-spec-nth-qubit chip-spec q0)) + (connect-hardware-objects chip-spec link (chip-spec-nth-qubit chip-spec q1)) ;; store the descriptor in the link hardware-object for later reference - (setf (hardware-object-misc-data link) link-hash) - ;; and store the hardware-object into the chip specification - (vector-push-extend link (chip-spec-links chip-spec))))))))) + (setf (hardware-object-misc-data link) link-hash)))))))) (defun load-specs-layer (chip-spec specs-hash) "Loads the \"specs\" layer into a chip-specification object." @@ -375,8 +374,6 @@ (or (gethash "isa" hash-table) (error 'missing-isa-layer-error)))) (chip-spec (make-chip-specification - :objects (make-array 2 :initial-contents (list (make-adjustable-vector) - (make-adjustable-vector))) :generic-rewriting-rules (coerce (global-rewriting-rules) 'vector)))) ;; set up the self-referential compilers (install-generic-compilers chip-spec (list ':cz)) diff --git a/src/chip/chip-specification.lisp b/src/chip/chip-specification.lisp index f256fd880..eba5b2a49 100644 --- a/src/chip/chip-specification.lisp +++ b/src/chip/chip-specification.lisp @@ -18,7 +18,8 @@ (defconstant +forest-error-sentinel+ -1 "The Forest database sometimes stores a fidelity of -1 to indicate that the fidelity estimation routine failed.") -(defstruct chip-specification +(defstruct (chip-specification + (:constructor %make-chip-specification)) "Houses information about hardware components on a QPU. OBJECTS is an array. In its nth position, there is a vector of order n hardware objects on the chip. @@ -28,9 +29,7 @@ GENERIC-COMPILERS is a vector of functions used as fallback compilation methods, GENERIC-REWRITING-RULES is a similar vector of REWRITING-RULE structures that the compressor loop can use to generate shorter gate strings when rules specialized to local hardware objects have been exhausted. Again, the array is sorted by descending preference. LOOKUP-CACHE is a hash table mapping lists of qubit indices to hardware objects. It gets auto-populated by `WARM-CHIP-SPEC-LOOKUP-CACHE'. This should not be accessed directly; use `LOOKUP-HARDWARE-ADDRESS-BY-QUBITS'." - (objects (make-array 2 :initial-contents (list (make-adjustable-vector) - (make-adjustable-vector))) - :type vector) + (objects (missing-arg) :type vector) (generic-compilers (make-adjustable-vector) :type vector) (generic-rewriting-rules (make-adjustable-vector) :type vector) (lookup-cache nil :type (or null hash-table))) @@ -39,6 +38,14 @@ LOOKUP-CACHE is a hash table mapping lists of qubit indices to hardware objects. (print-unreadable-object (cs stream :type t :identity nil) (format stream "of ~{~D~^:~} objects" (map 'list #'length (chip-specification-objects cs))))) +(defun make-chip-specification (&key (order 1) generic-rewriting-rules) + "Make a chip specification of order ORDER." + (%make-chip-specification + :objects (make-array (1+ order) + :initial-contents (loop :repeat (1+ order) + :collect (make-adjustable-vector))) + :generic-rewriting-rules generic-rewriting-rules)) + (defstruct permutation-record "Houses information about a permutation gate, for ease of lookup by the greedy scheduler. @@ -112,7 +119,8 @@ DURATION stores the measured gate duration (in nanoseconds)." hardware-object-rewriting-rules hardware-object-cxns hardware-object-misc-data)) -(defstruct hardware-object +(defstruct (hardware-object + (:constructor make-hardware-object (order))) "Houses information about a particular hardware object on a QPU. ORDER is a non-negative integer that counts the number of qubit subsidiaries of this hardware object. Equals (1- (length (vnth 0 (hardware-object-cxns this)))). (If you drew a schematic of a chip, this is also the dimension of the graphical representation of the hardware object: 0 for qubits, 1 for links, ... .) @@ -130,13 +138,47 @@ CXNS is an array. In its nth position, there is a vector of the order n hardware MISC-DATA is a hash-table of miscellaneous data associated to this hardware object: scratch data, scheduling hints (e.g., qubit coherence time), ... ." (order 0 :type unsigned-byte :read-only t) (gate-information (make-hash-table :test #'equalp)) - (compilation-methods (make-adjustable-vector)) - (permutation-gates (make-adjustable-vector)) - (rewriting-rules (make-adjustable-vector)) - (cxns (make-array 2 :initial-contents (list (make-adjustable-vector) - (make-adjustable-vector)))) + (compilation-methods (make-adjustable-vector) :type vector) + (permutation-gates (make-adjustable-vector) :type vector) + (rewriting-rules (make-adjustable-vector) :type vector) + (cxns (make-array 1 :initial-element nil :adjustable t) :type vector) (misc-data (make-hash-table :test #'equal) :type hash-table)) +(defun objects-on-hardware-object (order object) + "Get the objects of order ORDER connected to OBJECT." + ;; Make enough space. + (let ((cxns (hardware-object-cxns object))) + (when (<= (length cxns) order) + (setf (hardware-object-cxns object) + (adjust-array cxns (1+ order) :initial-element nil))) + (unless (vnth order cxns) + (setf (vnth order cxns) + (make-adjustable-vector))) + (vnth order cxns))) + +(defun connect-hardware-objects (chip-spec object1 object2) + "Connect hardware objects OBJECT1 onto OBJECT2 on CHIP-SPEC." + (let ((order1 (hardware-object-order object1)) + (order2 (hardware-object-order object2)) + (cxns1 (hardware-object-cxns object1)) + (cxns2 (hardware-object-cxns object2)) + (objects (chip-specification-objects chip-spec))) + ;; Make enough space. + (when (<= (length cxns1) order2) + (setf (hardware-object-cxns object1) + (adjust-array cxns1 (1+ order2) :initial-element nil))) + (when (<= (length cxns2) order1) + (setf (hardware-object-cxns object2) + (adjust-array cxns2 (1+ order1) :initial-element nil))) + (unless (vnth order2 cxns1) + (setf (vnth order2 cxns1) (make-adjustable-vector))) + (unless (vnth order1 cxns2) + (setf (vnth order1 cxns2) (make-adjustable-vector))) + (vector-push-extend (position object2 (vnth order2 objects)) + (vnth order2 cxns1)) + (vector-push-extend (position object1 (vnth order1 objects)) + (vnth order1 cxns2)))) + (defun hardware-object-native-instruction-p (obj instr) "Emits the physical duration in nanoseconds if this instruction translates to a physical pulse (i.e., if it is a native gate, \"instruction native\"), and emits NIL if this instruction does not admit direct translation to a physical pulse. @@ -173,6 +215,12 @@ Used to be an anonymous function associated to HARDWARE-OBJECT; now computed fro (defun chip-spec-links (chip-spec) "Get the links (as hardware-objects) for CHIP-SPEC." (vnth 1 (chip-specification-objects chip-spec))) +(defun chip-spec-objects-of-order (chip-spec order) + "Get the hardware objects of order N for CHIP-SPEC." + (vnth order (chip-specification-objects chip-spec))) +(defun chip-spec-order (chip-spec) + "Get the order the CHIP-SPEC, i.e. the highest order of any present hardware object on this chip." + (length (chip-specification-objects chip-spec))) (defun chip-spec-n-qubits (chip-spec) "Get the number of qubits on CHIP-SPEC. Equivalent to the largest qubit index @@ -181,6 +229,9 @@ used to specify CHIP-SPEC." (defun chip-spec-n-links (chip-spec) "Get the number of links on CHIP-SPEC." (length (chip-spec-links chip-spec))) +(defun chip-spec-n-objects-of-order (chip-spec order) + "Get the number of hardware objects of order ORDER on CHIP-SPEC." + (length (chip-spec-objects-of-order chip-spec order))) (defun chip-spec-nth-qubit (chip-spec n) "Get the Nth qubit on chip-spec (as a hardware-object)." @@ -188,21 +239,33 @@ used to specify CHIP-SPEC." (defun chip-spec-nth-link (chip-spec n) "Get the Nth link on chip-spec (as a hardware-object)." (vnth n (chip-spec-links chip-spec))) +(defun chip-spec-nth-object-of-order (chip-spec order n) + "Get the Nth hardware object of order ORDER on chip-spec (as a hardware-object)." + (vnth n (chip-spec-objects-of-order chip-spec order))) +(defun chip-spec-hw-objects-on-qubit (chip-spec order qubit-index) + "Get the objects of order ORDER associated with QUBIT-INDEX in CHIP-SPEC." + (objects-on-hardware-object order (chip-spec-nth-qubit chip-spec qubit-index))) (defun chip-spec-links-on-qubit (chip-spec qubit-index) "Get the links associated with QUBIT-INDEX in CHIP-SPEC." - (vnth 1 (hardware-object-cxns (chip-spec-nth-qubit chip-spec qubit-index)))) + (objects-on-hardware-object 1 (chip-spec-nth-qubit chip-spec qubit-index))) (defun chip-spec-qubits-on-link (chip-spec link-index) "Get the qubits associated with LINK-INDEX in CHIP-SPEC." - (vnth 0 (hardware-object-cxns (chip-spec-nth-link chip-spec link-index)))) + (objects-on-hardware-object 0 (chip-spec-nth-link chip-spec link-index))) +(defun chip-spec-qubits-on-object (chip-spec order index) + "Get the qubits associated with the hardware object of ORDER at INDEX in CHIP-SPEC." + (objects-on-hardware-object 0 (chip-spec-nth-object-of-order chip-spec order index))) (defun chip-spec-hw-object (chip-spec order address) "Get the hardware-object with matching ORDER and ADDRESS." (vnth address (vnth order (chip-specification-objects chip-spec)))) (defun chip-spec-adj-qubits (chip-spec qubit-index) - "Get the qubits adjacent (connected by a link) to QUBIT-INDEX in CHIP-SPEC." - (loop - :for link-index :across (chip-spec-links-on-qubit chip-spec qubit-index) - :append (remove qubit-index (coerce (chip-spec-qubits-on-link chip-spec link-index) 'list)))) + "Get the qubits adjacent (connected via any order hardware object) to QUBIT-INDEX in CHIP-SPEC." + (let ((qubits '())) + (loop :for order :from 1 to (chip-spec-order chip-spec) :do + (loop :for index :across (chip-spec-hw-objects-on-qubit chip-spec order qubit-index) + :do (setq qubits + (nunion qubits (coerce (chip-spec-qubits-on-object chip-spec order index) 'list))))) + (remove qubit-index qubits))) (defun chip-spec-adj-links (chip-spec link-index) "Get the links adjacent (connected by a qubit) to LINK-INDEX in CHIP-SPEC." (loop @@ -233,11 +296,15 @@ used to specify CHIP-SPEC." (hardware-object-dead-p (chip-spec-nth-link chip-spec link-index))) (defun lookup-hardware-address-by-qubits (chip-spec args) + "Use the qubit list ARGS to find the hardware object acting on those +qubits." (unless (chip-specification-lookup-cache chip-spec) (warm-chip-spec-lookup-cache chip-spec)) - (a:when-let ((hash-value (gethash args (chip-specification-lookup-cache chip-spec)))) - (destructuring-bind (index obj) hash-value - (values (1- (length args)) index obj)))) + (let ((value (gethash (sort (copy-list args) #'<) + (chip-specification-lookup-cache chip-spec)))) + (when value + (destructuring-bind (index obj) value + (values (1- (length args)) index obj))))) (defun lookup-hardware-address (chip-spec instr) "Finds a hardware object OBJ in CHIP-SPEC whose qubit resources match those used by INSTR. Returns the values object (ORDER ADDRESS OBJ), so that OBJ equals (vnth ADDRESS (vnth ORDER (chip-specification-objects CHIP-SPEC)))." @@ -272,9 +339,7 @@ used to specify CHIP-SPEC." (assert (/= qubit0 qubit1)) (assert (a:xor (null gate-information) (null type))) (setf type (a:ensure-list type)) - (let* ((obj (make-hardware-object - :order 1 - :cxns (vector (vector qubit0 qubit1) #())))) + (let* ((obj (make-hardware-object 1))) ;; set up the SWAP record (vector-push-extend (make-permutation-record :operator #.(named-operator "SWAP") @@ -314,6 +379,40 @@ used to specify CHIP-SPEC." ;; return the link obj)) +(defun build-hardware-object (qubits &key type gate-information) + "Constructs a template Nth order hardware object. The hardware object the ordered subset of interacting qubits this object works with directly. + + * The TYPE keyword can consist of (lists of) :CCNOT. This routine constructs a table of native gates based on 'templates' associated to each of these atoms, e.g., :CCNOT indicates that `CCNOT _ _ _` is native for this object. + + * The GATE-INFORMATION keyword is used to directly supply a hash table to be installed in the GATE-INFORMATION slot on the hardware object." + (dolist (qubit qubits) + (check-type qubit unsigned-byte)) + (check-type gate-information (or null hash-table)) + (assert (subsetp qubits (remove-duplicates qubits))) + (assert (a:xor (null gate-information) (null type))) + (setf type (a:ensure-list type)) + (let* ((n (length qubits)) + (obj (make-hardware-object (1- n)))) + + ;; this is the new model for setting up gate data + (when gate-information + (setf (hardware-object-gate-information obj) gate-information)) + + ;; this is the legacy model for setting up gate data + (flet ((stash-gate-record (atom gate-name parameters arguments fidelity) + (when (member atom type) + (setf (gethash (make-gate-binding :operator (named-operator gate-name) + :parameters parameters + :arguments arguments) + (hardware-object-gate-information obj)) + (make-gate-record :duration 150 + :fidelity fidelity))))) + (dolist (data `((:ccnot "CCNOT" () (_ _ _) 0.95d0))) + (destructuring-bind (atom gate-name parameters arguments fidelity) data + (stash-gate-record atom gate-name parameters arguments fidelity)))) + ;; return the object + obj)) + (defun build-qubit (q &key type gate-information) "Constructs a template qubit. The native gates for this qubit can be specified by one of two mutually exclusive means: @@ -323,7 +422,7 @@ used to specify CHIP-SPEC." * The GATE-INFORMATION keyword can be used to directly supply a hash table to be installed in the GATE-INFORMATION slot on the hardware object, allowing completely custom gateset control." (check-type gate-information (or null hash-table)) (assert (a:xor (null gate-information) (null type))) - (let ((obj (make-hardware-object :order 0))) + (let ((obj (make-hardware-object 0))) ;; new style of initialization (when gate-information (setf (hardware-object-gate-information obj) gate-information)) @@ -437,27 +536,40 @@ Compilers are listed in descending precedence.") (defun install-link-onto-chip (chip-specification q0 q1 &key (architecture (list ':cz))) "Adds a link, built using BUILD-LINK, between qubits Q0 and Q1 on the chip described by CHIP-SPECIFICATION. Returns the HARDWARE-OBJECT instance corresponding to the new link." - (let ((link (build-link q0 q1 :type architecture)) - (link-index (chip-spec-n-links chip-specification))) + (let ((link (build-link q0 q1 :type architecture))) (adjoin-hardware-object link chip-specification) - (vector-push-extend link-index (vnth 1 (hardware-object-cxns (chip-spec-nth-qubit chip-specification q0)))) - (vector-push-extend link-index (vnth 1 (hardware-object-cxns (chip-spec-nth-qubit chip-specification q1)))) + (connect-hardware-objects chip-specification + link + (chip-spec-nth-qubit chip-specification q0)) + (connect-hardware-objects chip-specification + link + (chip-spec-nth-qubit chip-specification q1)) ;; Return the link. link)) +(defun install-hardware-object-onto-chip (chip-specification qubits &key (architecture (list ':ccnot))) + "Adds a hardware object, built using BUILD-HARDWARE-OBJECT, across QUBITS on the chip described by CHIP-SPECIFICATION. Returns the HARDWARE-OBJECT instance corresponding to the new link." + (let* ((object (build-hardware-object qubits :type architecture)) + (order (hardware-object-order object))) + (adjoin-hardware-object object chip-specification) + (dolist (qubit qubits) + (connect-hardware-objects chip-specification + object + (chip-spec-nth-qubit chip-specification qubit))) + object)) + (defun warm-chip-spec-lookup-cache (chip-spec) "Warm the lookup cache of the CHIP-SPEC. This sets the table of the chip specification as a side effect. See the documentation of the `CHIP-SPECIFICATION' structure." (let ((hash (make-hash-table :test 'equalp))) - (loop :for q :across (chip-spec-qubits chip-spec) - :for index :from 0 - :do (setf (gethash (list index) hash) - (list index q))) - (loop :for l :across (chip-spec-links chip-spec) - :for index :from 0 - :for pair := (coerce (vnth 0 (hardware-object-cxns l)) 'list) - :for other-pair := (reverse pair) - :do (setf (gethash pair hash) (list index l)) - (setf (gethash other-pair hash) (list index l))) + (dotimes (order (length (chip-specification-objects chip-spec))) + (dotimes (index (chip-spec-n-objects-of-order chip-spec order)) + (let ((object (chip-spec-hw-object chip-spec order index))) + ;; Ignore ordering, just sort before accesses to this. + ;; Hardware objects are undirected. + (if (= order 0) + (setf (gethash (list index) hash) (list index object)) + (setf (gethash (sort (coerce (objects-on-hardware-object 0 object) 'list) #'<) hash) + (list index object)))))) (setf (chip-specification-lookup-cache chip-spec) hash) nil)) @@ -477,7 +589,7 @@ Compilers are listed in descending precedence.") (let ((gate-information (a:copy-hash-table (hardware-object-gate-information obj) :test #'equalp))) (dotimes (suborder order) - (loop :for subobject-address :across (vnth suborder (hardware-object-cxns obj)) + (loop :for subobject-address :across (objects-on-hardware-object suborder obj) :for subobject := (chip-spec-hw-object chip-specification suborder subobject-address) :do (dohash ((key val) (hardware-object-gate-information subobject)) (setf (gethash key gate-information) val)))) @@ -793,3 +905,15 @@ Compilers are listed in descending precedence.") (adjoin-hardware-object (build-qubit j :type '(:RZ :X/2 :MEASURE)) chip-spec)) (warm-hardware-objects chip-spec) chip-spec)) + +(defun build-ccnot-chip () + "Create a CHIP-SPECIFICATION with a single CCNOT gate. Used for testing >2 native qubit gates." + (let ((chip-spec (make-chip-specification + :order 2 + :generic-rewriting-rules (coerce (global-rewriting-rules) 'vector)))) + (install-generic-compilers chip-spec ':cz) + (dotimes (j 3) + (adjoin-hardware-object (build-qubit j :type '(:RZ :X/2 :MEASURE)) chip-spec)) + (install-hardware-object-onto-chip chip-spec '(0 1 2) :architecture :ccnot) + (warm-hardware-objects chip-spec) + chip-spec)) diff --git a/src/compressor/compressor.lisp b/src/compressor/compressor.lisp index de036d511..d51b49190 100644 --- a/src/compressor/compressor.lisp +++ b/src/compressor/compressor.lisp @@ -766,7 +766,7 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to (union ret (if (zerop order) (list address) - (coerce (vnth 0 (hardware-object-cxns obj)) 'list)))))))))) + (coerce (objects-on-hardware-object 0 obj) 'list)))))))))) ;; ;; this is a switch block containing all the different governor @@ -798,8 +798,9 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to (when (and (eq (first arg) ':global) (eq (second arg) ':global)) (let ((qubit-complex (union (global-queue-qubit-complex) - (coerce (vnth 0 (hardware-object-cxns - (chip-spec-hw-object chip-specification order address))) + (coerce (objects-on-hardware-object + 0 + (chip-spec-hw-object chip-specification order address)) 'list)))) (when (> (length qubit-complex) *global-queue-tolerance-threshold*) (transition-governor-state ':global ':global ':flushing)))) @@ -809,8 +810,9 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to ;; if we're a link, make sure our subgovernors are passing too (when (= 1 order) (let* ((subaddresses - (vnth 0 (hardware-object-cxns - (chip-spec-hw-object chip-specification order address)))) + (objects-on-hardware-object + 0 + (chip-spec-hw-object chip-specification order address))) (left-governor (nth (vnth 0 subaddresses) (first governors))) (right-governor (nth (vnth 1 subaddresses) (first governors))) (left-queue-contents @@ -974,7 +976,7 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to (t (let ((obj (chip-spec-hw-object chip-specification order address))) (dotimes (suborder order) - (dolist (subobj-index (coerce (vnth suborder (hardware-object-cxns obj)) 'list)) + (dolist (subobj-index (coerce (objects-on-hardware-object suborder obj) 'list)) (transition-governor-state suborder subobj-index ':flushing))))))) ;; ;; QUEUEING --> FLUSHING @@ -996,7 +998,7 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to (subgovernor (nth subaddress (nth suborder governors)))) (when (subsetp (if (= suborder 0) (list subaddress) - (coerce (vnth 0 (hardware-object-cxns subobj)) 'list)) + (coerce (objects-on-hardware-object 0 subobj) 'list)) qubit-complex) (assert (not (typep (first (governed-queue-contents subgovernor)) 'application))) (set-gq-fields subgovernor ':empty nil (make-null-resource)))))))) From aa13e8e8f08c0b1f139716e4abf52aed4ce3d7ef Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Thu, 16 Dec 2021 13:13:46 -0800 Subject: [PATCH 2/5] Bail out with a sensible error if we can't expand an instruction. Which usually manifests as an infinite recursive error. Setting up a proper condition class also allows us to handle this error in cases where the condition is not fatal. --- src/compressor/compressor.lisp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/compressor/compressor.lisp b/src/compressor/compressor.lisp index d51b49190..812958041 100644 --- a/src/compressor/compressor.lisp +++ b/src/compressor/compressor.lisp @@ -360,6 +360,22 @@ other's." ;; when we make it to this point, no rewrite rules apply, so quit. (peephole-rewriter-nodes->instrs (peephole-rewriter-node-next head))))) +;;; TODO: Specify some restarts to rebind the instruction count +;;; threshold if needed. +(define-condition expand-instruction-recursion-depth-exceeded () + () + (:report (lambda (condition stream) + (declare (ignore condition)) + (format stream "The instruction expansion threshold has ~ +been reached. +This probably means that the instruction expander has gone into a cycle. +Either: +1) There is no way to nativize this instruction for this chip specification. +2) The compiler is not able to find such a nativization.")))) + +(defvar *expand-instruction-count*) +(defparameter *expand-instruction-count-threshold* 1000) ; arbitrary + (defgeneric expand-instruction-to-native-instructions (instr chip) (:documentation "Expand INSTR into a list of instructions native for CHIP by applying nativization routines available on CHIP.") (:method ((instr instruction) chip) @@ -371,7 +387,12 @@ other's." nil) (:method ((instr gate-application) chip) - (let ((obj (lookup-hardware-object chip instr))) + ;; Instrument the instruction expander so we have a way of bailing + ;; out when we reach a certain recursion threshold. + (let ((*expand-instruction-count* (1+ *expand-instruction-count*)) + (obj (lookup-hardware-object chip instr))) + (when (> *expand-instruction-count* *expand-instruction-count-threshold*) + (error 'expand-instruction-recursion-depth-exceeded)) (if (and obj (hardware-object-native-instruction-p obj instr)) (list instr) (let ((instructions (apply-translation-compilers instr chip obj))) @@ -381,7 +402,13 @@ other's." (defun expand-to-native-instructions (instructions chip) "Expand INSTRUCTIONS into a list of instructions that are native for CHIP. Makes no attempt to perform rewiring or simplication." - (a:mappend (a:rcurry #'expand-instruction-to-native-instructions chip) instructions)) + ;; Make sure to bind *EXPAND-INSTRUCTION-COUNT* such that it tracks + ;; the recursive depth of EXPAND-INSTRUCTION-TO-NATIVE-INSTRUCTIONS + ;; rather than how many times it is called here. + (mapcan (lambda (instruction) + (let ((*expand-instruction-count* 0)) + (expand-instruction-to-native-instructions instruction chip))) + instructions)) (defun decompile-instructions-in-context (instructions chip-specification context) "This routine is called by COMPRESS-INSTRUCTIONS-IN-CONTEXT to make a decision about how to prefer 'linear algebraic compression': the list of INSTRUCTIONS can always be rewritten as its associated action matrix, but under certain conditions (governed by CONTEXT) we can sometimes get away with something less." From cdfb78fb713f667791dcb02491a7035d4e7b81e8 Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Fri, 17 Dec 2021 12:04:45 -0800 Subject: [PATCH 3/5] Be more precise about when to approximate single operation costs. * Distinguish between the cases of a hardware object being "dead" (which should strictly be declared in the chip specification), and the case of an object simply not having a "compiler path". (i.e. a sequence of compilation methods which sufficiently nativize given the gate set on the object). * Don't compute cost bounds for an instruction if doing so yields an infinite cycle in the compiler, since it may be the case that the compiler actually can nativize an instruction (e.g., but inserting the appropriate swaps), but cannot do so during chip specification warming. --- src/addresser/addresser-state.lisp | 41 ++++++++++++++++++++---------- src/chip/chip-specification.lisp | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/addresser/addresser-state.lisp b/src/addresser/addresser-state.lisp index 3cf51f426..b4898cadc 100644 --- a/src/addresser/addresser-state.lisp +++ b/src/addresser/addresser-state.lisp @@ -177,20 +177,33 @@ INSTR is the \"active instruction\". :do (dotimes (j (length order-list)) (let ((hw (vnth j order-list))) (unless (hardware-object-dead-p hw) - (let* ((instr (apply #'anon-gate "FLEX" (random-special-unitary (expt 2 qubits)) - (or (coerce (objects-on-hardware-object 0 hw) 'list) - (list j)))) - (instrs-decomposed (expand-to-native-instructions (list instr) chip-spec)) - (instrs-compressed (if *compute-tight-recombination-bound* - (compress-instructions instrs-decomposed chip-spec) - instrs-decomposed))) - (setf (addresser-state-initial-l2p state) (make-rewiring (chip-spec-n-qubits chip-spec)) - (addresser-state-working-l2p state) (make-rewiring (chip-spec-n-qubits chip-spec)) - (addresser-state-logical-schedule state) (make-lscheduler) - (addresser-state-chip-schedule state) (make-chip-schedule chip-spec)) - (append-instructions-to-lschedule (addresser-state-logical-schedule state) - instrs-compressed) - (funcall hardware-op hw)))))) + (unless (gethash "no-compiler-path-found" + (hardware-object-misc-data hw)) + (handler-case + ;; This code is optional; it's only to needed + ;; to help compute a bound on the single + ;; operation cost on this chip. This may be a + ;; source of non-determinism. + (let* ((instr (apply #'anon-gate "FLEX" (random-special-unitary (expt 2 qubits)) + (or (coerce (objects-on-hardware-object 0 hw) 'list) + (list j)))) + (instrs-decomposed (expand-to-native-instructions (list instr) chip-spec)) + (instrs-compressed (if *compute-tight-recombination-bound* + (compress-instructions instrs-decomposed chip-spec) + instrs-decomposed))) + (setf (addresser-state-initial-l2p state) (make-rewiring (chip-spec-n-qubits chip-spec)) + (addresser-state-working-l2p state) (make-rewiring (chip-spec-n-qubits chip-spec)) + (addresser-state-logical-schedule state) (make-lscheduler) + (addresser-state-chip-schedule state) (make-chip-schedule chip-spec)) + (append-instructions-to-lschedule (addresser-state-logical-schedule state) + instrs-compressed) + (funcall hardware-op hw)) + (expand-instruction-recursion-depth-exceeded (condition) + (declare (ignore condition)) + ;; We could have cycles (but the code is + ;; ultimately still compilable) because + ;; there's no way to insert swaps. + (values)))))))) (setf (addresser-state-initial-l2p state) initial-l2p (addresser-state-working-l2p state) working-l2p (addresser-state-logical-schedule state) lscheduler diff --git a/src/chip/chip-specification.lisp b/src/chip/chip-specification.lisp index eba5b2a49..9621a9fb3 100644 --- a/src/chip/chip-specification.lisp +++ b/src/chip/chip-specification.lisp @@ -584,7 +584,7 @@ Compilers are listed in descending precedence.") (compute-applicable-compilers (hardware-object-gate-information obj) (1+ order)))) (no-compiler-path () - (setf (gethash "dead" (hardware-object-misc-data obj)) t))) + (setf (gethash "no-compiler-path-found" (hardware-object-misc-data obj)) t))) ;; TODO: incorporate child object gatesets too (let ((gate-information (a:copy-hash-table (hardware-object-gate-information obj) :test #'equalp))) From faf897e6079b0ce6d7ba7f29b68188fda2cd9889 Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Fri, 17 Dec 2021 15:57:21 -0800 Subject: [PATCH 4/5] Don't try to do compression on chips with higher order objects. Not because it's impossible, but because the code really doesn't support it yet. --- src/compilation-methods.lisp | 13 ++++++++----- src/compressor/compressor.lisp | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compilation-methods.lisp b/src/compilation-methods.lisp index 4aa9aa74a..e254e689e 100644 --- a/src/compilation-methods.lisp +++ b/src/compilation-methods.lisp @@ -296,11 +296,14 @@ Returns a value list: (processed-program, of type parsed-program (local-topological-swaps (count-if #'swap-application-p straight-line-quil)) (fully-native-quil (expand-to-native-instructions straight-line-quil chip-specification)) (processed-quil fully-native-quil)) - (dotimes (n *compressor-passes*) - (format-noise "COMPILER-HOOK: Compressing, pass ~D/~D." (1+ n) *compressor-passes*) - (setf processed-quil - (compress-instructions processed-quil chip-specification - :protoquil (null registrant)))) + ;; order >1 hardware objects not yet supported by + ;; compressor. + (when (<= (length (chip-specification-objects chip-specification)) 2) + (dotimes (n *compressor-passes*) + (format-noise "COMPILER-HOOK: Compressing, pass ~D/~D." (1+ n) *compressor-passes*) + (setf processed-quil + (compress-instructions processed-quil chip-specification + :protoquil (null registrant))))) ;; we're done processing. store the results back into the CFG block. (setf (basic-block-code blk) processed-quil) (setf (basic-block-in-rewiring blk) initial-l2p) diff --git a/src/compressor/compressor.lisp b/src/compressor/compressor.lisp index 812958041..95cd1e4e7 100644 --- a/src/compressor/compressor.lisp +++ b/src/compressor/compressor.lisp @@ -806,7 +806,7 @@ This specific routine is the start of a giant dispatch mechanism. Its role is to ;; If you don't obey these, you're very likely to introduce subtle bugs. (transition-governor-state (order address new-state &optional arg) (when (and (typep order 'number) (> order 1)) - (format *error-output* "WARNING: No support for higher order hardware objects. Compressor queue may behave badly...~%")) + (error "No support for higher order hardware objects currently. Compressor queue may behave badly...~%")) (let* ((governed-queue (if (eq order ':global) global-governor From 58ce333074ec8f0680d8f80a6208968db0e48ca8 Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Fri, 17 Dec 2021 16:01:43 -0800 Subject: [PATCH 5/5] Add native CCNOT compilation test. This is to help ensure building chip specs and the bare minimum compilation of higher order objects keeps working. --- tests/compilation-tests.lisp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/compilation-tests.lisp b/tests/compilation-tests.lisp index 9c7a40424..4bb850bd1 100644 --- a/tests/compilation-tests.lisp +++ b/tests/compilation-tests.lisp @@ -195,6 +195,13 @@ CNOT 0 2")) ;; test on quality of compilation, and not on correctness. (is (>= 6 (length 2q-code))))) +(deftest test-native-ccnot-compilation () + "Test that we can compile a trivial program on a chip specification with a native CCNOT gate." + (let* ((chip (quil::build-ccnot-chip)) + (ccnot-prog (parse "CCNOT 0 1 2"))) + (not-signals error + (compiler-hook ccnot-prog chip)))) + (deftest test-cswap-compiles-with-qs () "Test that CSWAP compiles with QS-COMPILER. (Don't test the output's validity.)" (let ((result (quil::qs-compiler (quil::build-gate "CSWAP" () 0 1 2))))