-
Notifications
You must be signed in to change notification settings - Fork 72
/
ast.lisp
1893 lines (1607 loc) · 78.3 KB
/
ast.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;; src/ast.lisp
;;;;
;;;; Author: Robert Smith
(in-package #:cl-quil.frontend)
;;;;;;;;;;;;;;;;;;;;;;;;;; Atomic Elements ;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defstruct (qubit (:constructor qubit (index)))
"A qubit address."
(index nil :type unsigned-byte))
(defun qubit= (x y)
"Do the qubits X and Y have equal indices?"
(check-type x qubit)
(check-type y qubit)
(= (qubit-index x) (qubit-index y)))
(defstruct (memory-name (:constructor memory-name (region-name &optional descriptor)))
"A bare name of a memory region, used for LOAD and STORE operands."
(region-name nil :read-only t :type string)
;; The originating memory descriptor. Filled in during analysis.
(descriptor nil :type (or null memory-descriptor)))
(defstruct (memory-offset (:constructor memory-offset (offset)))
"A bare offset into a memory region, used for LOAD and STORE operands."
(offset nil :read-only t :type integer))
(defstruct (memory-ref (:constructor mref (name position &optional descriptor))
(:predicate is-mref))
"A reference into classical memory."
(name nil :read-only t :type string)
(position nil :read-only t :type unsigned-byte)
;; The originating memory descriptor. Filled in during analysis.
(descriptor nil :type (or null memory-descriptor)))
(defun memory-ref= (a b)
"Do the memory refs A and B represent the same memory ref?"
(check-type a memory-ref)
(check-type b memory-ref)
(and (string= (memory-ref-name a) (memory-ref-name b))
(= (memory-ref-position a) (memory-ref-position b))))
(defun memory-ref-hash (m)
(check-type m memory-ref)
#+sbcl
(sb-int:mix (sxhash (memory-ref-name m)) (sxhash (memory-ref-position m)))
#-sbcl
(logxor (sxhash (memory-ref-name m)) (sxhash (memory-ref-position m))))
(defmethod print-object ((mref memory-ref) stream)
(print-unreadable-object (mref stream :type t :identity nil)
(format stream "~A[~D]~:[~;*~]"
(memory-ref-name mref)
(memory-ref-position mref)
(memory-ref-descriptor mref))))
(defstruct (constant (:constructor constant (value &optional (value-type quil-real)))
(:predicate is-constant))
"A constant numerical value."
(value nil :type number)
(value-type quil-real :type quil-type))
(defun constant= (x y)
"Do the constants X and Y have equal types and values?"
(check-type x constant)
(check-type y constant)
(and (equal (constant-value-type x)
(constant-value-type y))
(= (constant-value x)
(constant-value y))))
(defstruct (label (:constructor label (name)))
"A label name. Corresponds to names prepended with `@' in Quil."
;; We allow an UNSIGNED-BYTE so that we can jump to absolute
;; positions in the program. This is *NOT* exposed in Quil directly.
(name nil :type (or string unsigned-byte)))
(defstruct (param (:constructor param (name))
(:predicate is-param))
"A formal parameter. Corresponds to names prepended with `%' in Quil. Represents a numerical value or a classical memory reference."
(name nil :read-only t :type string))
(defun param= (x y)
"Do parameters X and Y have the same name?"
(check-type x param)
(check-type y param)
(string= (param-name x)
(param-name y)))
(defstruct (formal (:constructor formal (name))
(:predicate is-formal))
"A formal argument. Represents a placeholder for a qubit or a memory reference."
(name nil :read-only t :type string))
(defun formal= (x y)
"Do formal arguments X and Y have the same name?"
(check-type x formal)
(check-type y formal)
(string= (formal-name x) (formal-name y)))
(defun argument= (x y)
"Are the (qubit or formal) arguments X and Y equal?"
(cond ((and (qubit-p x) (qubit-p y))
(qubit= x y))
((and (is-formal x) (is-formal y))
(formal= x y))
(t nil)))
;;; Memory descriptors are a part of the parsing process, but are
;;; defined in classical-memory.lisp.
(defstruct (delayed-expression (:constructor %delayed-expression))
"A representation of an arithmetic expression with fillable \"slots\".
PARAMS is a list of values to fill the slots with. These can be formal variables, but eventually must be a list of CONSTANTs.
LAMBDA-PARAMS is a list of symbols that EXPRESSION refers to.
EXPRESSION should be an arithetic (Lisp) form which refers to LAMBDA-PARAMS."
(params nil)
(lambda-params nil :read-only t)
(expression nil :read-only t))
(defun make-delayed-expression (params lambda-params expression)
"Make a DELAYED-EXPRESSION object initially with parameters PARAMS (probably a list of PARAM objects), lambda parameters LAMBDA-PARAMS, and the form EXPRESSION."
(check-type lambda-params symbol-list)
(%delayed-expression :params params
:lambda-params lambda-params
:expression expression))
(defun evaluate-delayed-expression (de &optional (memory-model-evaluator #'identity))
"Evaluate the delayed expression DE to a numerical value (represented in a CONSTANT data structure). MEMORY-MODEL is an association list with keys MEMORY-REF structures and values the value stored at that location."
(labels ((lookup-function (expr)
(if (valid-quil-function-or-operator-p expr)
expr
(error "Illegal function in arithmetic expression: ~A." expr)))
(evaluate-parameter (param)
(etypecase param
(constant (constant-value param))
(delayed-expression (constant-value (evaluate-delayed-expression param memory-model-evaluator)))))
(evaluate-expr (params lambda-params expression)
(etypecase expression
(memory-ref
(funcall memory-model-evaluator expression))
(cons
(let ((args (mapcar (lambda (e) (evaluate-expr params lambda-params e))
(cdr expression))))
(if (number-list-p args)
(apply (lookup-function (first expression)) args)
(cdr expression))))
(symbol
(cond
((eql expression 'pi)
pi)
((member expression lambda-params)
(evaluate-parameter (nth (position expression lambda-params)
params)))
(t
(error "Bad symbol ~A in delayed expression." expression))))
(number
expression))))
(let ((eval-attempt (evaluate-expr (delayed-expression-params de)
(delayed-expression-lambda-params de)
(delayed-expression-expression de))))
(if (typep eval-attempt 'number)
(constant eval-attempt)
de))))
(defun map-de-params (f de)
"Create a new DELAYED-EXPRESSION from DE, applying F to all of the parameters of DE."
(let ((c (copy-structure de)))
(setf (delayed-expression-params c) (mapcar f (delayed-expression-params de)))
c))
;;;;;;;;;;;;;;;;;;;;; Comment protocol for syntax tree objects ;;;;;;;;;;;;;;;;;;;;
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun make-comment-table ()
"Return an empty weak hash table suitable for use as the CL-QUIL::**COMMENTS** table.
This function can be used to re-initialize the **COMMENTS** table.
Keys are tested with EQ."
(tg:make-weak-hash-table :test 'eq :weakness ':key)))
(global-vars:define-global-var **comments**
(make-comment-table)
"Weak hash table populated with comments associated to different parts of an AST.
The keys are typically INSTRUCTION instances and associated values are STRINGs.")
(defun comment (x)
"Return the comment associated with X."
(values (gethash x **comments**)))
(defun (setf comment) (comment-string x)
(check-type comment-string string)
(setf (gethash x **comments**) comment-string))
;;;;;;;;;;;;;;;;;;;;;;;; Rewiring and Rewiring Comments ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; We store both the l2p and p2l vectors so that lookups in both
;; directions can be constant time. Because all of our qubits are in
;; the range 0...n-1, we can store these as vectors instead of hashmaps.
(defstruct (rewiring
(:constructor init-rewiring)
(:copier nil))
(l2p #() :type integeropt-vector)
(p2l #() :type integeropt-vector))
(defmethod print-object :around ((object rewiring) stream)
(let ((*print-pretty* nil))
(call-next-method)))
(defun make-rewiring-from-l2p (l2p)
"Safely extract a REWIRING from a logical-to-physical array."
(let ((p2l (make-array (length l2p) :initial-element nil)))
(dotimes (j (length l2p))
(let ((loc (aref l2p j)))
(when loc
(assert (and (< -1 loc (length p2l))) ()
"Malformed rewiring string: value ~A at position ~A is out of range." loc j)
(assert (null (aref p2l loc)) ()
"Malformed rewiring string: repeated value ~A at position ~A." loc j)
(setf (aref p2l loc) j))))
(init-rewiring :l2p l2p :p2l p2l)))
(defun make-rewiring-from-string (str)
"Safely extract a REWIRING from a string representation of an integer vector corresponding to the logical-to-physical mapping."
(assert (and (eql #\# (aref str 0))
(eql #\( (aref str 1))
(eql #\) (aref str (1- (length str)))))
nil
"Malformed rewiring string: input ~A is not of the form #(...)." str)
(let* ((stripped-string (string-trim "#()" str))
(tokens (first (tokenize stripped-string)))
(integer-vec
(map 'vector
(lambda (token)
(cond
((equalp (token-payload token) "nil")
nil)
((eql (token-type token) :integer)
(token-payload token))
(t
(error "Malformed rewiring string: unexpected token ~A." token))))
tokens)))
(make-rewiring-from-l2p integer-vec)))
(defun make-rewiring-pair-from-string (str)
"Safely extract a pair of REWIRINGs from a string representation of a CONS of two integer vectors."
(multiple-value-bind (matchedp matches)
;; This monstrosity matches strings of the form "(#(\d+ ...) . #(\d+ ...))"
(let ((match-int-vector
'(:REGISTER
(:SEQUENCE "#("
(:GREEDY-REPETITION 0 NIL
(:GROUP (:SEQUENCE :DIGIT-CLASS (:GREEDY-REPETITION 0 1 #\ ))))
#\)))))
(cl-ppcre:scan-to-strings
`(:SEQUENCE
:START-ANCHOR
#\(
,match-int-vector
(:GREEDY-REPETITION 0 NIL :WHITESPACE-CHAR-CLASS)
#\.
(:GREEDY-REPETITION 0 NIL :WHITESPACE-CHAR-CLASS)
,match-int-vector
#\)
:END-ANCHOR)
str))
(assert matchedp
nil
"Malformed rewiring pair string: ~@
input ~A is not of the form (#(...) . #(...))."
str)
(let ((first-rewiring-string (aref matches 0))
(second-rewiring-string (aref matches 1)))
(assert (= (length first-rewiring-string) (length second-rewiring-string))
nil
"Malformed rewiring pair string: length of rewirings don't match. ~@
first: ~A~@
second: ~A"
first-rewiring-string second-rewiring-string)
(values (make-rewiring-from-string first-rewiring-string)
(make-rewiring-from-string second-rewiring-string)))))
(a:define-constant +entering-rewiring-prefix+
"Entering rewiring: "
:test #'string=
:documentation "STRING prefix for \"entering rewiring\" comments. ")
(a:define-constant +exiting-rewiring-prefix+
"Exiting rewiring: "
:test #'string=
:documentation "STRING prefix for \"exiting rewiring\" comments. ")
(a:define-constant +entering/exiting-rewiring-prefix+
"Entering/exiting rewiring: "
:test #'string=
:documentation "STRING prefix for \"entering/exiting rewiring\" comments. ")
(defun comment-entering-rewiring-p (rewiring-string)
"Does REWIRING-STRING start with +ENTERING-REWIRING-PREFIX+?"
(uiop:string-prefix-p +entering-rewiring-prefix+ rewiring-string))
(defun comment-exiting-rewiring-p (rewiring-string)
"Does REWIRING-STRING start with +EXITING-REWIRING-PREFIX+?"
(uiop:string-prefix-p +exiting-rewiring-prefix+ rewiring-string))
(defun comment-entering/exiting-rewiring-p (rewiring-string)
"Does REWIRING-STRING start with +ENTERING/EXITING-REWIRING-PREFIX+?"
(uiop:string-prefix-p +entering/exiting-rewiring-prefix+ rewiring-string))
(defun %parse-rewiring (prefix rewiring-string make-rewiring)
"Call MAKE-REWIRING to parse a REWIRING from REWIRING-STRING after discarding PREFIX."
(funcall make-rewiring (subseq rewiring-string (length prefix))))
(defun parse-entering-rewiring (rewiring-string)
"Parse an entering REWIRING from REWIRING-STRING."
(%parse-rewiring +entering-rewiring-prefix+ rewiring-string #'make-rewiring-from-string))
(defun parse-exiting-rewiring (rewiring-string)
"Parse an exiting REWIRING from REWIRING-STRING."
(%parse-rewiring +exiting-rewiring-prefix+ rewiring-string #'make-rewiring-from-string))
(defun parse-entering/exiting-rewiring (rewiring-string)
"Parse entering and exiting REWIRINGs from REWIRING-STRING.
Return (VALUES ENTERING-REWIRING EXITING-REWIRING)."
(%parse-rewiring +entering/exiting-rewiring-prefix+
rewiring-string
#'make-rewiring-pair-from-string))
(defun rewiring-comment-type (rewiring-string)
"Return the type of the rewiring comment in REWIRING-STRING.
Possible return values are ':ENTERING, ':EXITING, and ':ENTERING/EXITING.
If REWIRING-STRING does not have a valid rewiring comment prefix, signal an error."
(cond ((comment-entering-rewiring-p rewiring-string)
':ENTERING)
((comment-exiting-rewiring-p rewiring-string)
':EXITING)
((comment-entering/exiting-rewiring-p rewiring-string)
':ENTERING/EXITING)
(t (error "Invalid rewiring comment: ~S" rewiring-string))))
(defun make-rewiring-comment (&key entering exiting)
"Make a rewiring comment from the given ENTERING and EXITING rewirings.
ENTERING and EXITING are both of type (OR NULL INTEGER-VECTOR REWIRING).
If both ENTERING and EXITING are non-null, make an :ENTERING/EXITING rewiring comment.
If only ENTERING is non-null, make an :ENTERING rewiring comment.
If only EXITING is non-null, make and :EXITING rewiring comment.
If both ENTERING and EXITING are null, signal an error."
(check-type entering (or null integer-vector rewiring))
(check-type exiting (or null integer-vector rewiring))
(when (typep entering 'rewiring)
(setf entering (rewiring-l2p entering)))
(when (typep exiting 'rewiring)
(setf exiting (rewiring-l2p exiting)))
(let ((*print-pretty* nil))
(cond ((and (not (null entering)) (not (null exiting)))
(format nil "~A(~A . ~A)" +entering/exiting-rewiring-prefix+ entering exiting))
((not (null entering))
(format nil "~A~A" +entering-rewiring-prefix+ entering))
((not (null exiting))
(format nil "~A~A" +exiting-rewiring-prefix+ exiting))
(t (error "MAKE-REWIRING-COMMENT: Both ENTERING and EXITING cannot be NULL.")))))
(defun instruction-rewirings (instruction)
"Return the pair of entering and exiting rewirings associated with instruction.
Return (VALUES ENTERING EXITING) if INSTRUCTION has a combined ENTERING/EXITING rewiring attached.
Return (VALUES ENTERING NIL) if INSTRUCTION has only an ENTERING rewiring.
Return (VALUES NIL EXITING) if INSTRUCTION has only an EXITING rewiring.
Return (VALUES NIL NIL) if INSTRUCTION has no rewiring attached."
(a:if-let ((comment (comment instruction)))
(ecase (rewiring-comment-type comment)
(:ENTERING (values (parse-entering-rewiring comment) nil))
(:EXITING (values nil (parse-exiting-rewiring comment)))
(:ENTERING/EXITING (parse-entering/exiting-rewiring comment)))
;; No comment attached to INSTRUCTION.
(values nil nil)))
(defun extract-final-exit-rewiring-vector (parsed-program)
"Extract the final exit rewiring comment from PARSED-PROGRAM and return it as a VECTOR.
If no exit rewiring is found, return NIL."
(check-type parsed-program parsed-program)
(loop :with code := (parsed-program-executable-code parsed-program)
:for i :from (1- (length code)) :downto 0
:for exiting-rewiring := (nth-value 1 (instruction-rewirings (aref code i)))
:when (not (null exiting-rewiring))
:return (rewiring-l2p exiting-rewiring)))
;;;;;;;;;;;;;;;;;;;;;;;; Pseudo-Instructions ;;;;;;;;;;;;;;;;;;;;;;;;;
(defclass jump-target ()
((label :initarg :label
:reader jump-target-label))
(:documentation "A target which can be jumped to. Corresponds to the LABEL directive."))
(declaim (inline jump-target-p))
(defun jump-target-p (x)
"Is X a jump target?"
(typep x 'jump-target))
(defclass include ()
((pathname :initarg :pathname
:reader include-pathname))
(:documentation "A directive to include another file in a Quil file."))
(defclass extern ()
((name :reader extern-name
:initarg :name
:documentation "The name of the operation being marked as an EXTERN"))
(:documentation "A directive to mark a particular operation as an extern. I.e. an
operation that does not have a definition. Names marked as EXTERN can
be parsed as they appear, and are protected from the optimizing
compiler, similar to the effect of a PRESERVE_BLOCK pragma.
NB: A name marked as an EXTERN will take priority over all other
names. Meaning if, for example, a DEFCIRCUIT is defined with name
marked as EXTERN, that circuit will be totally ignored by
compilation passes."))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Gate Definitions
;;; Note: In the future this might be expanded to include other objects.
(deftype lexical-context () '(or null token))
;; lexical-context is a generic function which should be implemented
;; for new token types. Note that this generic has methods defined
;; earlier than this point, for example in classical-memory.lisp.
(defmethod lexical-context (instr)
nil)
(defclass gate-definition ()
((name :initarg :name
:reader gate-definition-name)
(entries :initarg :entries
:reader gate-definition-entries)
;; This is a private slot and is here to increase the performance
;; of many repeated calculations of a GATE object. See the function
;; GATE-DEFINITION-TO-GATE.
(cached-gate :initform nil
:accessor %gate-definition-cached-gate)
(context :initarg :context
:type lexical-context
:accessor lexical-context))
(:metaclass abstract-class)
(:documentation "A representation of a raw, user-specified gate definition. This is *not* supposed to be an executable representation."))
(defgeneric gate-definition-qubits-needed (gate)
(:documentation "The number of qubits needed by GATE."))
(defclass matrix-gate-definition (gate-definition)
((entries :initarg :entries
:reader gate-definition-entries))
(:metaclass abstract-class)
(:documentation "A representation of a raw, user-specified gate definition. This is *not* supposed to be an executable representation."))
(defmethod gate-definition-qubits-needed ((gate matrix-gate-definition))
(ilog2 (isqrt (length (gate-definition-entries gate)))))
(defclass static-gate-definition (matrix-gate-definition)
()
(:documentation "A gate definition that has no parameters."))
(defclass parameterized-gate-definition (matrix-gate-definition)
((parameters :initarg :parameters
:reader gate-definition-parameters
:documentation "A list of symbol parameter names."))
(:documentation "A gate definition that has named parameters."))
(defclass permutation-gate-definition (gate-definition)
((permutation :initarg :permutation
:reader permutation-gate-definition-permutation))
(:documentation "A gate definition whose entries can be represented by a permutation of natural numbers."))
(defclass sequence-gate-definition (gate-definition)
((sequence :initarg :sequence
:reader sequence-gate-definition-sequence
:documentation "List of gate operations describing the sequence")
(arguments :initarg :arguments
:reader sequence-gate-definition-arguments
:documentation "List of arguments that appear in the sequence")
(parameters :initarg :parameters
:reader sequence-gate-definition-parameters
:documentation "List of parameters to be integrated into the sequence"))
(:documentation "Represents a gate definition as a sequence of other gates."))
(defclass exp-pauli-sum-gate-definition (gate-definition)
((terms :initarg :terms
:reader exp-pauli-sum-gate-definition-terms
:documentation "List of PAULI-TERMs comprising the sum.")
(parameters :initarg :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 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."))
(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)
(defmethod copy-instance ((term pauli-term))
(make-pauli-term :pauli-word (pauli-term-pauli-word term)
:prefactor (pauli-term-prefactor term)
:arguments (pauli-term-arguments term)))
(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 sequence-gate-definition))
(length (sequence-gate-definition-arguments gate)))
(defmethod gate-definition-qubits-needed ((gate permutation-gate-definition))
(ilog2 (length (permutation-gate-definition-permutation gate))))
(defun permutation-from-gate-entries (entries)
"Create the permutation (list of natural numbers) that represents the input matrix ENTRIES. Return nil if ENTRIES cannot be represented as a permutation."
(let* ((n (isqrt (length entries)))
(perm (make-list n)))
(dotimes (i n perm)
(let ((found-one nil))
(dotimes (j n)
(case (pop entries)
((0.0d0) nil)
((1.0d0) (cond
((or found-one (nth j perm))
(return-from permutation-from-gate-entries nil))
(t
(setf (nth j perm) i)
(setf found-one t))))
(otherwise (return-from permutation-from-gate-entries nil))))
(unless found-one
(return-from permutation-from-gate-entries nil))))))
(defun make-gate-definition (name parameters entries &key context)
"Make a static or parameterized gate definition instance, depending on the existence of PARAMETERS."
(check-type name string)
(check-type parameters symbol-list)
(if parameters
(make-instance 'parameterized-gate-definition
:name name
:parameters parameters
:entries entries
:context context)
(a:if-let ((perm (permutation-from-gate-entries entries)))
(make-instance 'permutation-gate-definition
:name name
:permutation perm
:context context)
(make-instance 'static-gate-definition
:name name
:entries entries
:context context))))
;;; Circuit Definitions
(defclass circuit-definition ()
((name :initarg :name
:reader circuit-definition-name)
(parameters :initarg :parameters
:reader circuit-definition-parameters)
(arguments :initarg :arguments
:reader circuit-definition-arguments)
(body :initarg :body
:reader circuit-definition-body)
(context :initarg :context
:type lexical-context
:accessor lexical-context)))
(defun make-circuit-definition (name params args body &key context)
(check-type name string)
(assert (every #'is-param params))
(assert (every #'is-formal args))
(make-instance 'circuit-definition
:name name
:parameters params
:arguments args
:body body
:context context))
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Instructions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Instructions and their protocol.
(defclass instruction ()
()
(:documentation "Abstract class representing an executable instruction.")
(:metaclass abstract-class))
(defclass classical-instruction (instruction)
()
(:metaclass abstract-class))
(defgeneric arguments (instruction)
(:documentation "Return a simple vector of arguments to an instruction."))
(defgeneric mnemonic (instruction)
(:documentation "Return the string mnemonic and base class name of the instruction."))
(defun instructionp (x)
"Is X an instruction?"
(typep x 'instruction))
;;; Now for the actual instructions.
(defclass no-operation (instruction)
()
(:documentation "The \"do-nothing\" instruction.")
;; The singleton-class is disabled rather than removed here (and elsewhere) as a reminder that
;; this is a quick fix. Ideally, we'd like to find a way to preserve the singleton nature of these
;; AST classes, but still work with rewiring comments and the guts of logical-schedule. See
;; https://github.com/rigetti/quilc/issues/270 for more context.
#+#:appleby-sufficiently-classy
(:metaclass singleton-class))
(defmethod arguments ((instruction no-operation)) #())
(defmethod mnemonic ((instruction no-operation)) (values "NOP" 'no-operation))
(defclass pragma (instruction)
((words :initarg :words
:reader pragma-words
:documentation "A list of strings derived from identifiers or numbers. It must start with a string.")
(freeform-string :initarg :freeform-string
:reader pragma-freeform-string
:documentation "A freeform string."))
(:default-initargs :words nil :freeform-string nil)
(:documentation "A compiler/interpreter pragma. Usually reserved for non-standard extensions to Quil that don't affect its interpretation."))
(defmethod mnemonic ((inst pragma)) (values "PRAGMA" (class-name (class-of inst))))
(defun make-pragma (words &optional freeform)
"Return a pragma, possibly specialized based on WORDS and FREEFORM."
(assert (and (listp words)
(not (endp words))))
(assert (stringp (first words))
((first words)))
(assert (every (a:disjoin #'stringp #'integerp) words)
(words))
(check-type freeform (or null string))
(specialize-pragma
(make-instance 'pragma :words words :freeform-string freeform)))
(defclass halt (instruction)
()
(:documentation "An instruction to immediately halt all execution.")
#+#:appleby-sufficiently-classy
(:metaclass singleton-class))
(defun haltp (x)
"Is X a HALT instruction?"
(typep x 'halt))
(defmethod arguments ((instruction halt)) #())
(defmethod mnemonic ((instruction halt)) (values "HALT" 'halt))
(defclass reset (instruction)
()
(:documentation "An instruction to reset all qubits to the |0>-state.")
#+#:appleby-sufficiently-classy
(:metaclass singleton-class))
(defmethod arguments ((instruction reset)) #())
(defmethod mnemonic ((instruction reset)) (values "RESET" 'reset))
(defclass reset-qubit (instruction)
((target :initarg :target
:accessor reset-qubit-target))
(:documentation "An instruction to reset a specific qubit into the |0>-state.
If the targeted qubit is entangled with other qubits the resulting action on the wavefunction is non-deterministic,
as the reset is formally equivalent to measuring the qubit and then conditionally applying a NOT gate."))
(defmethod arguments ((instruction reset-qubit)) (vector (reset-qubit-target instruction)))
(defmethod mnemonic ((instruction reset)) (values "RESET" 'reset-qubit))
(defclass wait (instruction)
()
(:documentation "An instruction to wait for some signal from a classical processor.")
#+#:appleby-sufficiently-classy
(:metaclass singleton-class))
(defmethod arguments ((instruction wait)) #())
(defmethod mnemonic ((instruction wait)) (values "WAIT" 'wait))
;;; Classical Instructions
(defclass unary-classical-instruction (classical-instruction)
((target :initarg :target
:reader classical-target))
(:documentation "An instruction representing a unary classical function.")
(:metaclass abstract-class))
(defmethod arguments ((inst unary-classical-instruction))
(vector (classical-target inst)))
(defclass binary-classical-instruction (classical-instruction)
((left :initarg :left
:reader classical-left-operand
:accessor classical-target)
(right :initarg :right
:accessor classical-right-operand))
(:documentation "An instruction representing a binary classical function.")
(:metaclass abstract-class))
(defmethod arguments ((inst binary-classical-instruction))
(vector (classical-left-operand inst)
(classical-right-operand inst)))
(defclass trinary-classical-instruction (classical-instruction)
((target :initarg :target
:reader classical-target)
(left :initarg :left
:reader classical-left-operand)
(right :initarg :right
:reader classical-right-operand))
(:documentation "An instruction representing a trinary classical function.")
(:metaclass abstract-class))
(defmethod arguments ((inst trinary-classical-instruction))
(vector (classical-target inst)
(classical-left-operand inst)
(classical-right-operand inst)))
(defgeneric specialize-types (instruction memory-descriptors)
(:documentation "Specialize the types that a classical instruction represents.")
;; By default, just return the instruction itself. It's a sensible
;; default for almost all instructions, and lets us simply call
;; SPECIALIZE-TYPES idempotently, as well as call it within the type
;; checker.
(:method (instruction memory-descriptors)
(declare (ignore memory-descriptors))
instruction))
(eval-when (:compile-toplevel :load-toplevel :execute)
(global-vars:define-global-var **mnemonic-types**
(make-hash-table :test 'equal)
"A map between mnemonic instruction strings and a list of possible argument types represented as vectors of type symbols.")
(defun mnemonic-addressing-modes (mnemonic-string)
"Given a Quil instruction mnemonic MNEMONIC-STRING, return the different \"addressing modes\" of the instruction as a list.
Each addressing mode will be a vector of symbols:
IMMEDIATE: immediate value
BIT*, OCTET*, INTEGER*, REAL*: A reference/address/name to a particular data type.
BIT, OCTET, INTEGER, REAL: A memory lookup/dereference to a particular data type.
"
(values (gethash mnemonic-string **mnemonic-types**)))
(global-vars:define-global-var **classical-instruction-class-argument-types**
(make-hash-table :test 'eq)
"A map between a class name (symbol) and the vector of types.")
(defun make-typed-name (name types)
(a:format-symbol (symbol-package name)
"~A-~{~A~^/~}"
name
types))
(defun valid-type-symbol-p (s)
(member s '(
;; Immediate values
immediate
;; Type names
bit integer real octet
;; Typed references
bit* integer* real* octet*)))
(defun memory-descriptors->type-resolver (descriptors)
"Given a sequence of memory descriptors, produce a function which takes a name as a string, and returns the type (a QUIL-TYPE) associated with that name, or an error."
(lambda (name)
(check-type name string)
(let ((descr (find name descriptors :key #'memory-descriptor-name
:test #'string=)))
(when (null descr)
(cerror "Return NIL."
"Couldn't determine the type associated with ~S"
name)
nil)
(memory-descriptor-type descr))))
(defun argument-type-matches-p (resolver type arg)
"Given a type resolver (as if returned by MEMORY-DESCRIPTORS->TYPE-RESOLVER), along with a symbolic type name TYPE and an argument ARG (that would be found as an argument to any of the classical instructions), check that ARG conforms to the type specification TYPE."
(etypecase arg
;; Only TYPE* satisfy names, but we need to check TYPE.
(memory-name
(adt:match quil-type (funcall resolver (memory-name-region-name arg))
(quil-bit (eq 'bit* type))
(quil-octet (eq 'octet* type))
(quil-integer (eq 'integer* type))
(quil-real (eq 'real* type))))
;; Only bare types are allowed for memory refs.
(memory-ref
(adt:match quil-type (funcall resolver (memory-ref-name arg))
(quil-bit (eq 'bit type))
(quil-octet (eq 'octet type))
(quil-integer (eq 'integer type))
(quil-real (eq 'real type))))
;; Only immediate's satisfy CONSTANT arguments.
(constant
(case type
(immediate t)
(otherwise nil)))
;; If we encounter a FORMAL, we haven't properly expanded.
(formal (error "Can't runtime type check a formal because ~
circuits haven't been expanded."))))
(defun argument-types-match-p (resolver types args)
"Check that all of the arguments ARGS (a sequence) conform to each of the respective type specifications TYPES (a sequence, specifically the symbolic types as seen below), all according to the resolver."
(every (lambda (type arg) (argument-type-matches-p resolver type arg))
types
args))
;; function to take a type tuple and produce a COND-compatible test.
(defun expand-classical-instruction-definitions (name mnemonic num-args superclass docstring types)
(check-type name symbol)
(check-type mnemonic string)
(check-type num-args (integer 1))
(check-type superclass symbol)
(check-type docstring string)
(check-type types list)
`(progn
;; Base instruction class. Can be specialized.
(defclass ,name (,superclass)
()
(:documentation ,docstring))
;; The type specializer.
(defmethod specialize-types ((instr ,name) memory-descriptors)
(let ((args (arguments instr))
(resolver (memory-descriptors->type-resolver memory-descriptors)))
(cond
,@(loop :for type-tuple :in types
:for typed-name := (make-typed-name name type-tuple)
:collect `((argument-types-match-p resolver ',type-tuple args)
(change-class instr ',typed-name)))
(t (cerror "Return the original instruction."
"Could not specialize the type of ~A." instr)
;; TODO This error is unhelpful.
instr))))
;; The mnemonic for this group of instructions.
(defmethod mnemonic ((inst ,name)) (values ',mnemonic ',name))
;; Each typed instantiation.
,@(loop :for type-tuple :in types
:for typed-name := (make-typed-name name type-tuple)
:do (assert (and (= num-args (length type-tuple))
(every #'valid-type-symbol-p type-tuple)))
:collect typed-name :into typed-names
:append (list
;; Leaf class.
`(defclass ,typed-name (,name)
()
(:documentation
,(format nil "A ~A specialized on (~{~A~^, ~})."
name
type-tuple)))
;; Since we specialize on the superclass, we
;; will also specialize on the subclass, even
;; though we have an overarching method
;; specializing on T. We don't want to
;; re-type-specialize something that has
;; already been specialized.
`(defmethod specialize-types ((instr ,typed-name) memory-descriptors)
(declare (ignore memory-descriptors))
;; Do nothing. Already specialized.
instr)
;; Store the argument types in various fashions.
`(setf (gethash ',typed-name **classical-instruction-class-argument-types**)
',(coerce type-tuple 'vector))
`(pushnew ',(coerce type-tuple 'vector)
(gethash ',mnemonic **mnemonic-types**)
:test 'equalp))))))
(defun classical-instruction-argument-types (instruction)
"Given a classical instruction instance INSTRUCTION, return the types of the arguments."
(let ((class-name (class-name (class-of instruction))))
(or (gethash class-name **classical-instruction-class-argument-types**)
(error "The instruction class ~A doesn't have an associated ~
argument type."
class-name))))
;;; Ok, so DEFINE-CLASSICAL-INSTRUCTION will generate a base class for
;;; the instruction associated with the mnemonic, along with a variety
;;; of type-specifc instructions, sometimes referred to as the various
;;; "addressing modes" of the mnemonic. (This isn't exactly right, but
;;; close enough for our analogy.)
;;;
;;; Why do this? Why not parameterize instructions on mnemonic-type
;;; pairs? In other words, why can't we have some general notion of a
;;; CLASSICAL-AND instruction, along with a handful of addressing
;;; modes like (OCTET, OCTET) and (OCTET, IMMEDIATE)? The reason is
;;; that we want our base instruction classes to refer to specific
;;; operational semantics. The (OCTET, OCTET) mode has distinct
;;; operational semantics from (OCTET, IMMEDIATE).
;;;
;;; In other parts of CL-QUIL, we will find it useful to work with
;;; this parameterization, however. So the functions MNEMONIC as well
;;; as CLASSICAL-INSTRUCTION-ARGUMENT-TYPES provide this facility.
(defmacro define-classical-instruction (name mnemonic &body body)
(check-type mnemonic string)
(multiple-value-bind (types decls docstring)
(a:parse-body body :documentation t)
;; Declarations are not allowed.
(assert (null decls))
;; An empty body, or a body with non-lists aren't allowed.
(assert (and (not (endp types))
(every #'listp types)))
(let ((num-args (length (first types))))
;; All specializations must be the same length.
(assert (every (lambda (spec) (= num-args (length spec))) types))
(expand-classical-instruction-definitions
name
mnemonic
num-args
(ecase num-args
((1) 'unary-classical-instruction)
((2) 'binary-classical-instruction)
((3) 'trinary-classical-instruction))
docstring
types))))
(define-classical-instruction classical-negate "NEG"
"The arithmetic negation instruction."
(integer)
(real))
(define-classical-instruction classical-not "NOT"
"The bit toggling instruction."
(octet)
(integer)
(bit))
(define-classical-instruction classical-and "AND"
"An instruction representing bitwise AND."
(octet octet)
(octet immediate)
(integer integer)
(integer immediate)
(bit bit)
(bit immediate))
(define-classical-instruction classical-inclusive-or "IOR"
"An instruction representing bitwise IOR."
(octet octet)
(octet immediate)
(integer integer)
(integer immediate)
(bit bit)
(bit immediate))
(define-classical-instruction classical-exclusive-or "XOR"
"An instruction representing bitwise XOR."
(octet octet)
(octet immediate)
(integer integer)
(integer immediate)
(bit bit)
(bit immediate))
(define-classical-instruction classical-move "MOVE"
"An instruction representing the movement of a value to another address."
(octet immediate)
(octet octet)
(integer immediate)
(integer integer)
(real immediate)
(real real)
(bit immediate)
(bit bit))