-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathplot.lisp
4302 lines (4010 loc) · 203 KB
/
plot.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
;;;Plotting
(in-package "CL-USER")
;;;Plot a few diamonds, showing edges of processor volumes, etc.
(defvar *loop-positions*)
;;Plot the world sheet of a single diamond, annotated with the edges of our simulation volume.
;;We project into the plane of the diamond by ignoring the t coordinate. We then rotate coordinates so that the y
;;coordinate on the plot is in the direction of phat + qhat and the x coordinate in the direction of qhat - phat
(defun plot-diamond (diamond &rest gnuplot-keys
&key ((:dump-time *dump-time*) *dump-time*) (steps 20) &allow-other-keys)
(let* ((p (diamond-p diamond))
(p5 (make-pq5 p))
(q (diamond-q diamond))
(q5 (make-pq5 q))
(phat (3vector-normalize p))
(qhat (3vector-normalize q))
(x (3vector-normalize (3vector- qhat phat)))
(y (3vector-normalize (3vector+ phat qhat)))
(delta (make-pq5 (4vector- (compute-diamond-end-flat diamond) (diamond-end diamond)))) ;end - flat-end
(togo (make-togo (diamond-start diamond))) ;offsets to ijkl = 1, plus dump line
(tostart (4vector- (xtoi (diamond-start diamond)))) ;offsets to ijkl = 0
(lines ;Make 5 curves
(loop for end in '(nil t) ;Start and end of our volume
nconc (loop for index below (if (and *dump-time* end) 5 4)
as pj = (aref p5 index)
as qj = (aref q5 index)
as deltaj = (aref delta index)
as offset = (aref (if end togo tostart) index)
collect (loop for step below steps
for value = (- (* 1.2 (/ step steps)) 0.1) ;From -0.1 to 1.1
when (if (> (abs qj) (abs pj)) ;QJ bigger so it's better to let A vary
(let ((b (/ (- offset (* pj value)) (- qj (* value deltaj)))))
(and (< -0.1 b 1.1) (list value b)))
(let ((a (/ (- offset (* qj value)) (- pj (* value deltaj))))) ;Other way: let B vary
(and (< -0.2 a 1.2) (list a value))))
collect it))))
(points '((0.0 0.0) (0.0 1.0) (1.0 1.0) (1.0 0.0) (0.0 0.0))) ;(a b) coordinates of corners
(styles '("linespoints" "lines lt 1 lw 1" "lines lt 2 lw 1" "lines lt 3 lw 1" "lines lt 4 lw 1"
"lines lt 1 lw 2" "lines lt 2 lw 2" "lines lt 3 lw 2" "lines lt 4 lw 2")))
(if *dump-time* (setq styles (append styles '("lines lt -1")))) ;Style for dumping
(flet ((plot-coordinates (&optional a b) ;Give position for plotting from (a b)
(and a ;Pass on NIL
(let ((position (diamond-position diamond :a a :b b)))
(values (3vector-dot position x) (3vector-dot position y) (4vector-t position))))))
(apply
#'gnuplot
(1+ (length lines))
(max 5 steps)
#'(lambda (plot point)
(if (eq point :title)
(if (= plot 9)
(format nil "T = ~A" (format-float-reasonably *dump-time*))
(if (zerop plot)
(format nil "~S" diamond)
(nth (1- plot) '("I=0" "J=0" "K=0" "L=0" "I=1" "J=1" "K=1" "L=1"))))
(apply #'plot-coordinates
(cond ((zerop plot) ;The diamond itself
(nth point points))
(t (nth point (nth (1- plot) lines)))))))
:prelude (format nil "~:{set label '~C' at ~{~F, ~F, ~F~} offset character 0.3,0.3~%~}"
(loop for char across "srel" ;Same order as in POINTS
for ab in points
collect (list char (multiple-value-list (apply #'plot-coordinates ab)))))
:styles styles
gnuplot-keys))))
;;This plots in space-time and doesn't know about curved diamonds
(defun old-plot-diamond (diamond &rest gnuplot-keys &key ((:dump-time *dump-time*) *dump-time*) &allow-other-keys)
(let* ((p (make-pq5 (diamond-p diamond)))
(q (make-pq5 (diamond-q diamond)))
(togo (make-togo (diamond-start diamond))) ;offsets to ijkl = 1, plus dump line
(tostart (4vector- (xtoi (diamond-start diamond)))) ;offsets to ijkl = 0
(lines ;Make 5 lines of 2 points each
(loop for end in '(nil t) ;Start and end of our volume
nconc (loop for index below (if (and *dump-time* end) 5 4)
as pj = (aref p index)
as qj = (aref q index)
as offset = (aref (if end togo tostart) index)
collect (loop for value in '(-1d0 2d0)
collect (if (> (abs qj) (abs pj))
(diamond-position
diamond :a value :b (/ (- offset (* pj value)) qj))
(diamond-position
diamond :a (/ (- offset (* qj value)) pj) :b value))))))
(points (list (diamond-start diamond) (diamond-right diamond) (diamond-end diamond)
(diamond-left diamond) (diamond-start diamond))))
(multiple-value-bind (minima maxima)
(diamond-bounding-box diamond)
(let ((ranges (loop for index below 3
as min = (aref minima index)
as max = (aref maxima index)
as range = (- max min)
collect (cons (- min range fudge-coordinates)
(+ max range fudge-coordinates))))
(styles '("linespoints" "lines lt 1 lw 1" "lines lt 2 lw 1" "lines lt 3 lw 1" "lines lt 4 lw 1"
"lines lt 1 lw 2" "lines lt 2 lw 2" "lines lt 3 lw 2" "lines lt 4 lw 2")))
(if *dump-time* (setq styles (append styles '("lines lt -1")))) ;Style for dumping
(apply
#'gnuplot (1+ (length lines))
5
#'(lambda (plot point)
(if (eq point :title)
(if (= plot 9)
(format nil "T = ~A" (format-float-reasonably *dump-time*))
(nth plot '("diamond" "I=0" "J=0" "K=0" "L=0" "I=1" "J=1" "K=1" "L=1")))
(cond ((zerop plot) ;The diamond itself
(values-list (3vector-list (nth point points))
)
)
((> point 1) nil)
(t (values-list (3vector-list
(nth point (nth (1- plot) lines))))))))
:prelude (format nil "~:{set label '~C' at ~{~F, ~F, ~F~} offset character 0.3,0.3~%~}"
(list (list #\s (3vector-list (diamond-start diamond)))
(list #\l (3vector-list (diamond-left diamond)))
(list #\r (3vector-list (diamond-right diamond)))
(list #\e (3vector-list (diamond-end diamond)))))
:styles styles
:xrange (first ranges)
:yrange (second ranges)
:zrange (third ranges)
gnuplot-keys)))))
;;Plot a set of diamonds
(defun plot-diamonds (diamonds &rest gnuplot-keys)
(let* ((points-list ;List of lists of boundary points
(mapcar
#'(lambda (diamond) ;Get list of points around diamond
(let ((points (list (diamond-left diamond)
(diamond-start diamond)
(diamond-right diamond)
(diamond-end diamond))))
(push (car (last points)) points))) ;Make loop
diamonds)))
(apply #'gnuplot (length points-list) (loop for points in points-list maximize (length points))
#'(lambda (plot point)
(if (eq point :title) (format nil "~S" (nth plot diamonds))
(let ((location (nth point (nth plot points-list))))
(and location (values-list (3vector-list location))))))
:styles :linespoints
gnuplot-keys)))
;;Plots intersection of a given time and the strings that we know about.
(defun plot-time-slice (&rest gnuplot-keys &key (time (current-time)) cell (style "lines lt 1")
marks ;Mark where loops were deleted
range
title
&allow-other-keys)
(let ((data (get-plot-data :time time :range range))
(outline (and cell (unit-cell-outline))))
(format t "~&~D string~:P with ~D segments in all~%" (length data)
(reduce #'+ (map 'list #'length data)))
(setq data (coerce data 'simple-vector))
(let* ((length (length data)) ;Number of segments to plot
(mark-plot (and marks length)) ;Plot # for marks
(outline-plot (and outline (if marks (1+ length) length))) ;Plot # for outline
(styles (make-list length :initial-element style))) ;All same color
(when mark-plot
(setq styles (append styles '("points lt 3"))))
(when outline
(setq styles (append styles '("lines lt 2"))))
(apply #'gnuplot
(+ (if outline 1 0) (if marks 1 0) length)
(max (* 4 (length outline)) (* 3 (length marks)) (loop for string across data maximize (length string)))
#'(lambda (plot point)
(unless (eq point :title)
(let ((vector
(cond ((eq plot outline-plot) ;Outline
(multiple-value-bind (line part) (truncate point 4) ;Two blank lines between segments
(nth part (nth line outline))))
((eq plot mark-plot)
(multiple-value-bind (line part) (truncate point 3) ;Two blank lines between segments
(and (zerop part)
(nth line marks))))
(t ;Regular data
(let* ((string (aref data plot))
(length (length string)))
(and (< point length) (aref string point)))))))
(and vector
(values-list (3vector-list vector))))))
:title (or title (format nil "Time ~A" (format-float-reasonably time)))
:styles styles
:3d t
(append (and range
(list :xrange (cons (3vector-x (first range)) (3vector-x (second range)))
:yrange (cons (3vector-y (first range)) (3vector-y (second range)))
:zrange (cons (3vector-z (first range)) (3vector-z (second range)))))
gnuplot-keys)))))
;;Get data for plotting: a list of vectors of positions
(defun get-plot-data (&key (time (current-time)) range velocities-p)
(let ((data nil))
(map-string-paths
#'(lambda (diamond &rest rest)
(let* ((path (apply #'get-path time diamond rest)) ;Get path of string
(positions (get-plot-positions path (first range) (second range) velocities-p))) ;Get positions
(unless (zerop (length positions)) ;Empty string or all outside range
(push positions data)))))
data))
;;Make a cubical range for plotting
(defun make-range (min max)
(list (make-3vector min min min) (make-3vector max max max)))
(defun plot-unit-cell ()
(let ((data (unit-cell-outline)))
(gnuplot 1 (* 4 (length data))
#'(lambda (plot point)
(declare (ignore plot))
(unless (eq point :title)
(multiple-value-bind (line part) (truncate point 4) ;Two blank lines between segments
(let ((result (nth part (nth line data))))
(and result (values-list (3vector-list result)))))))
:3d t
:styles :lines)))
;;Accepts a list of (DIAMOND A1 B1 A2 B2)
;;Returns a vector of global positions to be plotted, with 2 consecutive NIL's where there is a break.
;;This function works whether or not the string is a loop
;;If velocities set, put velocity between each pair of positions.
(defun get-plot-positions (string &optional minima maxima velocities-p)
(let ((data (make-array 100 :fill-pointer 0)))
(loop for (diamond a1 b1 a2 b2) in string
with previous = nil
for start = (diamond-position-wrap diamond a1 b1) ;Start of the segment
for end = (diamond-position-wrap diamond a2 b2) ;End of the segment
do
(when minima ;only including range?
(multiple-value-setq (start end) (segment-inside-range start end minima maxima)))
(when start ;Nothing to do if string was entirely outside
(cond ((null previous) ;First time: start with start point
(vector-push-extend start data))
((3vector= start previous fudge-global-coordinates) ;Previous end same as start?
) ;Don't need start point
(t ;There is a previous, but it is not the same as this point,
(vector-push-extend nil data) ;Break line here and start again
;(vector-push-extend nil data) ;gnuplot splot requires two blank lines or else you get cross-lines
(vector-push-extend start data)))
;;Always use end point. If there's a break between the first start and the last end, we need both.
;;If not, we still need both, so that the loop will close in the plot
(when velocities-p
(vector-push-extend (diamond-velocity diamond) data))
(vector-push-extend end data)
(setq previous end)))
data))
;;Return the portion of the segment that lies inside the given box, if any
(defun segment-inside-range (start end minima maxima)
(if (point-in-box start minima maxima 3) ;Start is inside?
(if (point-in-box end minima maxima 3) ;End too?
(values start end) ;Yes, nothing to do
(values start (line-box-intersection start end minima maxima)))
(if (point-in-box end minima maxima 3) ;Start is outside. What about end?
(values (line-box-intersection end start minima maxima) end) ;End inside
(let ((results (line-box-intersections end start minima maxima)))
(when results ;NIL if doesn't intersect
(unless (= (length results) 2)
(error "We expected 2 intersections or none, but there were ~D" (length results)))
(values-list results))))))
(defun line-box-intersection (from to minima maxima)
(let ((results (line-box-intersections from to minima maxima)))
(unless (= (length results) 1)
(error "We expected 1 intersection, but there were ~D" (length results)))
(first results)))
;;Return place or places where line intersects box
(defun line-box-intersections (from to minima maxima)
(let ((results nil))
(dotimes (direction 3) ;Check faces perpendicular to this direction
(let ((denominator (- (aref to direction) (aref from direction))))
(unless (< (abs denominator) fudge-global-coordinates) ;Parallel to edge: say no intersection
(flet ((try (value)
(let ((parameter (/ (- value (aref from direction)) denominator)))
(when
(and (< 0.0 parameter 1.0) ;Valid parameter range
;;Find position of intersection
(let ((position (4vector+ (4vector-scale from (- 1.0 parameter))
(4vector-scale to parameter))))
(when (loop for index below 3
always (or (= index direction) ;Don't check direction we just solved
(and (> (+ (aref position index) fudge-global-coordinates)
(aref minima index))
(< (- (aref position index) fudge-global-coordinates)
(aref maxima index)))))
(unless (loop for previous in results ;Already there?
thereis (4vector= previous position fudge-global-coordinates))
(push position results)))))))))
(try (aref minima direction))
(try (aref maxima direction))))))
results))
;;Get diamond position, taking into account the possibility that the diamond may have wrappings between corners
;;The position is standardized relative to the start
;;All diamond corners should have been standardized on reading
(defun diamond-position-wrap (diamond a b)
(let* ((start (diamond-start diamond)) ;Wrap all corners so that they are near start
(end (standardize-position (diamond-end diamond) start))
(left (standardize-position (diamond-left diamond) start))
(right (standardize-position (diamond-right diamond) start)))
;;Now the diamond does not have any wrappings between its corners. Get position the usual way
(cond ((and (= a 0.0) (= b 0.0)) start) ;Special cases are important to overlap-diamond
((and (= a 0.0) (= b 1.0)) right)
((and (= a 1.0) (= b 1.0)) end)
((and (= a 1.0) (= b 0.0) left))
(t (let ((a1 (- 1.0 a))
(b1 (- 1.0 b)))
(4vector+ (4vector-scale start (* a1 b1))
(4vector-scale end (* a b))
(4vector-scale left (* a b1))
(4vector-scale right (* b a1))))))))
;;Diamond position, but wrap if we are reading dumps
(defun diamond-position-wrap-dumps (diamond &key a b)
(if *reading-dumps* (diamond-position-wrap diamond a b)
(diamond-position diamond :a a :b b)))
;;Edges of diamond with wrapping
(mirror-images
(defun diamond-a-wrap (diamond)
(let ((start (diamond-start diamond)))
(4vector- (standardize-position (diamond-left diamond) start) start)))
(defun diamond-new-a-wrap (diamond)
(let ((right (diamond-right diamond)))
(4vector- (standardize-position (diamond-end diamond) right) right))))
;;Give the amount of wrapping as we go over the loop
(defun loop-wrapping (start-diamond)
(loop with start = (diamond-right start-diamond)
with position = start
for diamond = (diamond-e start-diamond) then (diamond-e diamond) ;Walk over loop to east
do (setq position (standardize-position (diamond-right diamond) position)) ;Standardize each w.r.t. last
until (eq diamond start-diamond)
finally (return (3vector- position start))))
;;Get velocity vector of this diamond (at the start, in case of expansion), taking into account wrapping
(defun diamond-velocity (diamond)
(let* ((start (standardize-position (diamond-start diamond))) ;Wrap corners so that they are near start
(left (standardize-position (diamond-left diamond) start))
(right (standardize-position (diamond-right diamond) start)))
;; v = (unit p + unit q)/2. Normalize each to 1/2, then add
(if (and (zerop (3vector-length (3vector- left start)))
(zerop (3vector-length (3vector- right start))))
0.0
(if (zerop (3vector-length (3vector- left start)))
(3vector-normalize (3vector- right start) 0.5)
(if (zerop (3vector-length (3vector- right start)))
(3vector-normalize (3vector- left start) 0.5)
(3vector+ (3vector-normalize (3vector- left start) 0.5) (3vector-normalize (3vector- right start) 0.5)))))))
;;Read and plot a snapshot
;;LOOP-POSITIONS is an array of loop positions or T to read them
(defun plot-read-dumps (directory time &rest gnuplot-keys &key loop-positions &allow-other-keys)
(when loop-positions
(unless (or (integerp time) (= time (float (round time) time)))
(error "Time ~F is not an integer" time))
(setq time (round time))
(when (eq loop-positions t)
(setq loop-positions (read-loop-positions directory (1- time) time)))
(setq gnuplot-keys (append `(:marks ,(aref loop-positions time)) gnuplot-keys)))
(read-dumps directory :time time)
(apply #'plot-time-slice :time time gnuplot-keys))
(defun make-movie (directory start end step &rest gnuplot-keys)
(loop for time from start by step below end
for count from 0
for title = (format nil "Time ~A" (format-float-reasonably time))
do (format t "~A~%" title)
do (apply #'plot-read-dumps directory time
:prelude (format nil "set output '~A/plot-~D.jpg'~%set terminal jpeg~%"
directory count)
:title title :key nil
:exit t
gnuplot-keys)))
(defun make-movie-submit (directory start end step &rest gnuplot-keys)
(setq directory (merge-pathnames directory batch-root-directory))
(loop for time from start by step below end
for count from 0
for title = (format nil "Time ~A" (format-float-reasonably time))
for file = (format nil "~A/plot-command-~D.lisp" directory count)
do (with-group-write-access
(with-open-file (stream file :direction :output :if-exists :supersede :if-does-not-exist :create)
(write
`(plot-read-dumps ,directory ,time
:prelude ,(format nil "set output '~A/plot-~D.jpg'~%set terminal jpeg~%"
directory count)
:title ,title :key nil
;; :loop-positions t
:exit t
,@(loop for item in gnuplot-keys
collect `',item))
:stream stream))) ;Put command into file
(do-submit directory (format nil "plot-~D" count) (format nil "plot-~D-" count) batch-flags
`(load ,file))))
;;Histogram loop sizes read from dump
;;We plot dL/d(ln l), where dL and dn are the total length and the number of loops whose lengths are between l and l+dl
;;so dL = l dn.
;;According to Alex and Tanmay, dn/dl goes as l^{-5/2}, so dn/d(ln l) goes as l^{-3/2}, and dL/dl as l^{-1/2}
(defun histogram-loop-sizes (&rest gnuplot-keys &key (lengths (path-lengths)) (bins 20) (min 1.0) (max *total-size*)
&allow-other-keys)
(setq max (float max 0.0))
(let* ((bin-size (/ (log (/ max min)) bins)) ;Logarithmic size of a bin
(data (make-array bins :element-type 'double-float :initial-element 0.0)) ;Total length in this bin
(infinite-total 0.0)
(finite-total 0.0)
(short-total 0.0)
)
(dolist (length lengths)
(declare (double-float length))
(if (> length min)
(let ((index (fixnum-floor (/ (real-log (/ length min)) bin-size))))
(cond ((>= index bins) ;Too large?
(incf infinite-total length))
(t
(incf finite-total length)
(incf (aref data index) (/ length bin-size (total-3volume)) ;Convert to dL/(dV d(ln l))
))))
(incf short-total length)))
(format t "~&Length in short loops: ~$, finite: ~$ infinite: ~$, total ~$"
short-total finite-total infinite-total (+ short-total finite-total infinite-total))
(flet ((fit (l)
(* 0.4 (expt l -0.5) ;Fit from paper, but 0.2 seems to be closer to what we have
;;In VV paper, all cells have unit length of string. But we have only about 1/sqrt{2} if the
;;string turns a corner. This computes our average length. 0.13 and 0.22 should be rationals
(/ (+ (* (sqrt 0.5) 0.22) 0.13) (+ 0.22 0.13))
)))
(apply #'gnuplot
2 bins
#'(lambda (plot point)
(if (eq point :title) (nth plot '("data" "VV paper result"))
(ecase plot
(0 (let ((length (* min (exp (* bin-size (+ point 0.5))))) ;Central length value
(total (aref data point))) ;Total length in loops in this bin
(and (plusp total) ;no point, rather than 0 if no data
(values-list (list length total)))))
(1 (case point
(0 (values min (fit min)))
(1 (values max (fit max)))
(t nil))))))
:styles :linespoints
:logscale '(:x :y)
gnuplot-keys))))
;;New spectrum plotting
;;Parameter m in nonuniform FT. See Fourier.tex.
(defparameter *nufft-m* 8)
(mirror-images
;;Advance (west for A, east for B) to the next diamond with a different A or different B
;;If you do this for both A and B from the same point, you get places that will see each other in the future.
;;This returns a diamond that has a SE rather than a NE. Thus if there are several diamonds that have the same
;;A sides, this returns the latest in time. If reconnections
;;decreased the length of the side, this is the shortest one.
;;If we reach something that is not a diamond, return it
;;The meanings of PREVIOUS and NEXT were reversed in November 2015.
(defun diamond-next-a (diamond)
(if (diamondp diamond)
(or (diamond-nw diamond) ;New A? Return it
(diamond-next-a (diamond-sw diamond)))
diamond))
;;Reverse direction: east for A, west for B. This gives places that have seen each other in the past.
;;if there are several diamonds that have the same A sides, this returns the earliest in time.
(defun diamond-previous-a (diamond)
(or (diamond-se diamond) ;New A? Return it
(diamond-previous-a (diamond-ne diamond)))) ;Same A: recurse
;;Returns values:
;; A-HATS: SIMPLE-VECTOR with the values of the unit tangent 3-vector a'
;; DSIGMAS: (SIMPLE-ARRAY DOUBLE-FLOAT (*)) with length of corresponding a'
;; CREATED: (SIMPLE-ARRAY DOUBLE-FLOAT (*)) when kink between this a' and next was generated.
;;The A-HAT vectors are in order going to the west, meaning that each vector is applied after the previous one.
;;In the mirror image case, the B-HAT vectors go around to the east, with the same meaning.
;;We work along the future edge of the existing world sheet, so the total spatial change in a and the total change in b
;;are equal for a closed, non-wrapping loop, and for such a loop in the rest frame, they are both zero.
;;This also means that if there are intersections in the past, we will not be confused by them.
;;If SKIP is 0 (the default), the first tangent vector will be the one for the current diamond, or its furthest
;;NE neighbor (which will be the same unless there are intersections).
;;If SKIP > 0, we will skip forward the given number of a' values relative to that. If SKIP < 0 we skip backward
;;If this is called for A and B with the same parameters, and SKIP > 0, you get segments that have not seen each
;;other. If SKIP < -COUNT, you get segments that have seen each other.
;;The order of returned vectors and thus the meaning of positive and negative SKIP were reversed in November 2015.
;;If CLOSE is set, we will adjust the vectors to make sure that the total of hat*sigma is the same as the
;;original total of the diamond-a's, which might not otherwise happen because of numerical errors.
(defun get-a-data-dsigmas (diamond &key count skip (close t))
(unless skip (setq skip 0))
(when count (setq close nil)) ;Don't bother if only using part of the loop
;;We have to make sure here to get a diamond that begins a new a going westward, so we will find it again if loop
(cond ((plusp skip) ;Skipping forward (to left)
(loop repeat skip do (setq diamond (diamond-next-a diamond)))) ;Skip this many.
(t ;Backward or no skip. First go back one more than skip requested.
(loop repeat (- 1 skip) do (setq diamond (diamond-previous-a diamond)))
(setq diamond (diamond-next-a diamond)))) ;Now forward so we are at the beginning of a stretch of a
(loop with this = diamond ;First count a's in loop
do (setq this (diamond-next-a this)) ;Move to next diamond
count t into loop-count
when (eq this diamond) ;Looped? Each A in loop counted once
return (setq count loop-count) ;Found loop before count ran out
until (and count (= count loop-count))) ;Reached max without looping? Use given count
(loop with a-hats = (make-array count) ;Now we'll store exactly COUNT data
with dsigmas = (make-array count :element-type 'double-float)
with created = (make-array count :element-type 'double-float)
with total-a = (make-zero-3vector)
with this = diamond
for index from 0 below count
for a = (diamond-new-a-wrap this) ;Future edge
for dsigma = (* 2 (3vector-t a)) ;Use time as sigma, so loop closes in time properly
unless (plusp dsigma)
do (warn "Zero length segment in ~S" this)
unless (fudge= (3vector-length a) (3vector-t a) fudge-coordinates)
do (warn "~D has non-null side, length=~F, t=~F, difference ~S" this (3vector-length a) (3vector-t a)
(/ (- (3vector-t a) (3vector-length a)) (3vector-t a)))
do
(3vector-incf total-a a) ;Get 3vector total of sides
(setf (aref a-hats index) (3vector-normalize a)) ;Store present a'. Normalize so it is always unit
(setf (aref dsigmas index) dsigma) ;Store length
(setf (aref created index) (4vector-t (diamond-a-kink-created this))) ;Kink from this to new diamond
(setq this (diamond-next-a this)) ;Move to next diamond
finally (return
(values (if close
(close-hats a-hats :dsigmas dsigmas ;Fix up hats to give right total a
:goal (3vector-scale total-a 2.0)) ;double because a side is half hat*dsigma
a-hats)
dsigmas created))))
(defun get-a-data-sigmas (diamond &key count skip smooth-range smooth-interval)
(multiple-value-bind (hats dsigmas created) (get-a-data-dsigmas diamond :count count :skip skip)
(if smooth-range ;Smooth out data?
(multiple-value-bind (new-hats new-sigmas)
(smooth-data hats (dsigma-to-sigma dsigmas) smooth-range smooth-interval)
(values new-hats new-sigmas created))
(values hats (dsigma-to-sigma dsigmas) created))))
;;Get just the a-hats, checking that the a-dsigmas are equal
(defun get-a-hats (diamond &key (tolerance fudge-coordinates))
(multiple-value-bind (a-hats a-dsigmas) (get-a-data-dsigmas diamond)
(loop with average = (/ (total-sigma a-dsigmas) (length a-dsigmas))
for dsigma across a-dsigmas
minimize dsigma into min
maximize dsigma into max
finally
(let ((spread (/ (- max min) average)))
(when (> spread tolerance)
(error "Fractional spread of ~A delta-sigma values is ~S" :a spread))))
a-hats))
;;Check a loop for diamonds whose sides aren't null
(defun check-non-null-a (diamond &key (tolerance fudge-coordinates))
(setq diamond (diamond-next-a diamond)) ;forward so we are at the beginning of a stretch of a
(loop with worst = nil
and worst-error
with this = diamond
for a = (diamond-new-a-wrap this) ;Future edge
for time = (3vector-t a)
for length = (3vector-length a)
for error = (abs (- time length))
count t into segments
unless (fudge= time length tolerance)
count t into bad
and sum error into total-error
and when (or (null worst) (> error worst-error))
do (setq worst this worst-error error)
do (setq this (diamond-next-a this)) ;Move to next diamond
until (eq this diamond)
finally (format t "Out of ~D ~A segments, ~[all are null~:;~:*~D are non-null with average error ~S, worst ~S~]~%"
segments :a bad (and (plusp bad) (/ total-error bad)) worst-error)
(return worst)))
;;Return length sigma and count of segments in A
(defun loop-length-and-count-a (start-diamond)
(setq start-diamond (diamond-next-a start-diamond)) ;Advance to diamond that starts a new A
(loop with diamond = start-diamond
do (setq diamond (diamond-next-a diamond)) ;Get next A segment
count t into count
;;The vector q points, say, from sigma=t=0 to sigma=t=q_t, since sigma-t is fixed along the right edge
;;Thus the argument of b(t+sigma) is 2 q_t. Check: x=(a+b)/2 so delta-x = delta-b/2
;;= (b(sigma_1) - b(sigma_0))/2 = b-hat (sigma_1-sigma_0)/2 so sigma_1-sigma_0 = 2 |delta-x|
sum (* 2 (3vector-length (diamond-a-wrap diamond))) into length
when (eq diamond start-diamond) ;Looped around? This last segment has been done
return (values length count)))
;;Return total of <(a - prev-a)^2> and count of segments
(defun loop-length-and-angle-a (start-diamond)
(setq start-diamond (diamond-next-a start-diamond)) ;Advance to diamond that starts a new A
(loop with diamond = start-diamond
with a with dsigma with a-hat
for previous-a = (3vector-normalize (diamond-a-wrap start-diamond)) then a-hat
do (setq diamond (diamond-next-a diamond) ;Advance to new diamond
a (diamond-a-wrap diamond) ;This a' vector
dsigma (* 2 (3vector-length a)) ;Amount of sigma here. See loop-length-and-count-a
a-hat (3vector-scale a (/ 2 dsigma))) ;unit tangent vector
sum dsigma into length
sum (* 2 (- 1.0 (3vector-dot previous-a a-hat))) into total-angle ;(a1 - a2)^2 = 2(1 - a1.a2)
when (eq diamond start-diamond) ;Looped around? This last segment has been done
return (values length total-angle)))
;;Find power spectrum of a single string loop, as defined by Fourier.tex.
;;Returns array with P(k) where element i corresponds to frequency i/l, where l is the length of the loop.
;;We always return 0 in slot 0, because P(0)=0 by definition
;;If EXTEND-K is given, we return frequencies through that value
(defun loop-power-a (start-diamond &optional extend-k)
(setq start-diamond (diamond-next-a start-diamond)) ;Advance to diamond that starts a new A
(multiple-value-bind (l nn) (loop-length-and-count-a start-diamond) ;Number of segments N in loop and total length L
(declare (double-float l)
(fixnum nn))
(let* ((needed (if extend-k (max nn (ceiling (* l extend-k) (* 2 pi)))
nn)) ;Normally give N frequencies, but can request more
;Size of FFT: At least twice needed frequencies, rounded up to power of 2
(n (expt 2 (1+ (ceiling (log needed 2)))))
(xi (make-array nn :element-type 'double-float)) ;Point locations for Fourier transform
(fi (make-array nn :element-type 'double-float)) ;Function values for Fourier transform
;;Our calculations will yield n frequencies ranging from -(n-1)/(2L) to n/(2L). But negative ones are just
;;the conjugates of positive ones and those in the larger half are not well approximated by our procedure.
;;So we return n/4 powers.
(power (make-array (/ n 4) :element-type 'double-float :initial-element 0.0)))
(locally
(declare (optimize speed)
(type (simple-array double-float (*)) xi fi power)
(fixnum n))
(dotimes (component 3)
(loop with sigma double-float = 0d0 ;sigma = 0 represents the end of this segment
with diamond = start-diamond
with a
and dsigma double-float
and a-hat
with index = 0
with previous-a of-type 3vector = (3vector-normalize (diamond-a-wrap start-diamond))
do (setq diamond (diamond-next-a diamond) ;Advance to new diamond
a (diamond-a-wrap diamond) ;This a' vector
dsigma (* 2 (3vector-length a)) ;Amount of sigma here. See loop-length-and-count-a
a-hat (and (plusp dsigma) (3vector-scale a (/ 2 dsigma)))) ;unit tangent vector
when a-hat
;;We come here with each pair of diamonds. The last time, diamond is the starting diamond
;;sigma is the coordinate at which one diamond gives way to the next
do (setf (aref xi index) (/ sigma l) ;Scale into 0..1
(aref fi index) (- (aref a-hat component) (aref previous-a component))) ;FT of this: see notes
and do (incf index)
and do (incf sigma dsigma) ;Advance over present diamond
and do (setq previous-a a-hat)
else do (warn "Zero length segment")
until (eq diamond start-diamond) ;Stop when start diamond has just been processed
)
(let ((spectrum (nufft xi fi *nufft-m* n))) ;Get Fourier components
(declare (type (simple-array double-float (*)) spectrum))
;;Frequency j in SPECTRUM (stored in slots 2j, 2j+1) corresponds to wave number k = 2 pi j/L.
;;The value is sum_i [a'_i - a'_{i-1}] e^{-i k sigma_i}.
;;Thus A(k) = 1/sqrt{2piL} (1/(ik)) spectrum(j).
;;We are trying to find P = 2 k |A|^2 = |spectrum(j)|^2 / (pi L k) = |spectrum(j)|^2 / (2 pi^2 j)
(loop for j from 1 below (the fixnum (/ n 4)) ;Skip freq 0
do (incf (aref power j)
;squared magnitude of complex number stored as 2 reals
(/ (+ (expt (aref spectrum (* 2 j)) 2) (expt (aref spectrum (1+ (* 2 j))) 2))
(* 2 pi pi j))))))
(values power l)))))
) ;mirror-images
(defun get-ab-data-sigmas (diamond)
(multiple-value-bind (a-hats a-sigmas) (get-a-data-sigmas diamond)
(multiple-value-bind (b-hats b-sigmas) (get-b-data-sigmas diamond)
(values a-hats a-sigmas b-hats b-sigmas))))
(defun get-ab-data-dsigmas (diamond)
(multiple-value-bind (a-hats a-sigmas) (get-a-data-dsigmas diamond)
(multiple-value-bind (b-hats b-sigmas) (get-b-data-dsigmas diamond)
(values a-hats a-sigmas b-hats b-sigmas))))
;;Get hats and sigmas from loop and call function with given arguments
(defun call-with-hats (diamond function &rest args)
(multiple-value-bind (a-hats a-sigmas b-hats b-sigmas) (get-ab-data-sigmas diamond)
(apply function a-hats a-sigmas b-hats b-sigmas args)))
;;Here when the function is supposed to be applied to the arguments
(defun apply-with-hats (diamond function &rest args)
(multiple-value-bind (a-hats a-sigmas b-hats b-sigmas) (get-ab-data-sigmas diamond)
;;args is a list of arguments, the last one of which is supposed to be spread, so spread ARGS and call APPLY to spread last arg
(apply #'apply function a-hats a-sigmas b-hats b-sigmas args)))
;;Add one power spectrum to bins. Add P(k) to DATA, k values to KS.
;;K here is always k, not kt.
(defun bin-power-1 (data counts ks k-min k-max k-bins power l)
(loop with bin-size = (/ (log (/ k-max k-min)) k-bins)
for j from 1 below (length power) ;Skip freq 0. Index j corresponds and wave number k = 2 pi j/L.
for p = (aref power j)
for k = (/ (* 2 pi j) l)
for bin = (floor (/ (log (/ k k-min)) bin-size))
when (and (>= bin 0) (< bin k-bins))
do
(incf (aref data bin) p)
(incf (aref counts bin))
(incf (aref ks bin) k)))
;;Spectrum in high-K limit is <(a_i - a_{i+1})^2> / (pi k <seg size>) = (total (a-a)^2)/(pi total-length)
;;We return the coefficient
(defun high-k-power (&key (min-length 0.0) (scaling t))
(let ((total-length 0.0)
(total-angle 0)) ;Total <a . a>
(map-string-paths
#'(lambda (&rest ignore) (declare (ignore ignore))) ;Ignore open strings
#'(lambda (diamond) ;Loops
(multiple-value-bind (l angle) (loop-length-and-angle-a diamond)
(when (> l min-length)
(incf total-length l)
(incf total-angle angle)
(multiple-value-bind (lb angle) (loop-length-and-angle-b diamond)
(unless (< (/ (- l lb) l) 0.01)
(warn "A length is ~F, but B length is ~F" l lb)
(incf total-length l)
(incf total-angle angle)))))))
(let ((result (/ total-angle pi total-length)))
(if scaling (* result (current-time)) result)))) ;In scaling case, user will want to divide by kt
(mirror-images
;; Fourier series of a'(sigma)
(defun slow-right-moving-k-spectrum (path k)
(loop for component below 3
for sigma = 0d0
collect (/ (loop for segment in path
for dL = (apply #'path-segment-length segment)
for p-hat = (3vector-normalize (4to3vector (diamond-a (car segment))))
do (incf sigma dL)
sum (* (aref p-hat component)
(/ (exp (* #C(0.0 -1.0)
k
sigma))
(* #C(0.0 1.0) k))
(- (exp (* #C(0.0 1.0)
k
dL))
1.0)))
(sqrt (* 2 pi sigma)))))
;; power spectrum of path for wavenumber k
(defun slow-right-moving-power (path k)
(let ((spectrum (slow-right-moving-k-spectrum path k))
)
(loop for component below 3
sum (abs (* 2
k
(nth component spectrum)
(conjugate (nth component spectrum)))))))
)
;;Get power for single wavenumber by direct integration
(defun slow-power (k &optional (min-length 3.0))
(let ((total-power 0)
(total-length 0))
(map-string-paths
#'(lambda (&rest ignore) (declare (ignore ignore))) ;Ignore open strings
#'(lambda (diamond)
(let* ((path (get-path (current-time) diamond))
(length (path-length path)))
(when (> length min-length)
(incf total-length length)
(mirror-images ;Add right and left power
(incf total-power (* (slow-right-moving-power path k) length))) ;Weight by length
))))
(/ total-power total-length 2) ;Extra 2 for A and B
))
(defstruct power
k-min
k-max
extend-k
data ;Average power in each bin
ks) ;Average k value in each bin
;;Read dumps, process power and two-point-function as requested
;;This works by calling start-process-power and start-process-two-point-function to get some closures which are
;;then called twice for each loop in the dump, with the loop, its length, the a- or b-power, and the total a or b,
;;and once at the end with no arguments, to say were finished
;;If EXTEND-K, we Fourier transform enough points to give that value of k. Otherwise we use only as many
;;frequencies as there are points, rounded up to a power of 2.
(defun process-dumps (directory time
&key (power t) (two-point-function t) ;What to do
(delta-min 1d-3) (delta-max 1d5) (two-point-bins 80)
(k-min 1d-3) (k-max 1d2) (power-bins 80)
extend-k ;This applies to both operations
min-length)
(setq directory (merge-pathnames directory batch-root-directory))
(cond ((or power two-point-function)
(read-dumps directory :time time)
(unless min-length (setq min-length *total-size*)) ;Now that we have read the dump, we can default this
(with-group-write-access
(let ((handlers nil))
(when power
(push (start-process-power directory time k-min k-max power-bins extend-k) handlers))
(when two-point-function
(push (start-process-two-point-function directory time delta-min delta-max two-point-bins extend-k)
handlers))
(map-string-paths
#'(lambda (&rest ignore) (declare (ignore ignore))) ;Ignore open strings
#'(lambda (diamond) ;Loops
(let ((l (loop-length diamond)))
(when (> l min-length)
(format t "Length ~$: " l)
(mirror-images ;A and B
(format t "~A " :a) (force-output)
(let ((power (loop-power-a diamond extend-k))
(total-a (loop-total-a diamond)))
(mapc #'(lambda (handler) (funcall handler diamond l power total-a)) handlers)))))))
(mapcar #'funcall handlers)
(terpri))))
(t (warn "PROCESS-DUMPS called with nothing to do"))))
;;Process all times available in this directory
(defun process-dumps-all (directory &rest keys)
(setq directory (merge-pathnames directory batch-root-directory))
(let ((times (dump-times directory)))
(format t "Times: ~S~%" times)
(dolist (time (reverse times)) ;Latest to earliest, so memory problems occur later
(format t "Time ~S:~%" time)
(apply #'process-dumps directory time keys))))
(defun start-process-power (directory time k-min k-max k-bins extend-k)
(let ((data (make-array k-bins :element-type 'double-float :initial-element 0d0))
(counts (make-array k-bins :element-type 'fixnum :initial-element 0)) ;Number of samples in each bin
(ks (make-array k-bins :element-type 'double-float :initial-element 0d0))) ;Total of k values in each bin
#'(lambda (&optional diamond l power total-a) ;Loop to process, or no arg when done
(declare (ignore total-a)) ;Only used by two-point-function
(if diamond
(bin-power-1 data counts ks k-min k-max k-bins power l)
;;Finished: write file
(with-open-file (stream (power-file time directory) :direction :output
:if-does-not-exist :create :if-exists :supersede)
(loop for index below k-bins ;Average power
for count = (aref counts index)
when (plusp count)
do (setf (aref data index) (/ (aref data index) count)
(aref ks index) (/ (aref ks index) count)))
(prin1 (make-power :k-min k-min :k-max k-max :extend-k extend-k
:data data :ks ks)
stream)
(pathname stream))))))
;;Read processed data from file
(defun read-power (directory time)
(with-open-file (stream (power-file time directory)) (read stream)))
;;Accept list of (DIRECTORY TIME &OPTIONAL TITLE K-UNIT) or just DIRECTORY and plot functions
;;In the latter case we use all times.
;;If no TITLE is given, we use the directory name and time
;;If LENGTH-SCALE is given, k values in data are multiplied by it.
(defun plot-power-1 (list &rest gnuplot-keys &key scaling bins &allow-other-keys)
(setq list (loop for entry in list
append (if (consp entry) (list entry)
(let* ((directory (merge-pathnames entry batch-root-directory))
(times (dump-times directory)))
(unless times
(error "No dumps were made in ~A" directory))
(mapcar #'(lambda (time) (list directory time)) times)))))
(loop with arbitrary-scaling = nil
for (directory time title length-scale) in list
for power = (read-power (merge-pathnames directory batch-root-directory) time)
when bins
do (setq power (rebin-power power bins))
collect (list power time length-scale (or title (format nil "~A: ~D" directory time))) into data-list
when length-scale do (setq arbitrary-scaling t)
maximize (length (power-data power)) into max-points
finally
(apply #'gnuplot (length data-list) max-points
#'(lambda (plot point)
(destructuring-bind (power time length-scale title) (nth plot data-list)
(if (eq point :title) title
(when (< point (length (power-data power)))
(let* ((p (aref (power-data power) point))
(k (aref (power-ks power) point)))
(unless (zerop p) ;don't plot points with no data
(if scaling (setq k (* k time))) ;Convert to kt for horizontal axis
(if length-scale (setq k (* k length-scale))) ;Multiply by arbitrary length unit
(values k p)))))))
:xlabel (cond (arbitrary-scaling "given units") (scaling "kt") (t "k"))
:ylabel "correlator"
:styles :linespoints
:logscale :x
;;Put lambda = 2pi/k on opposite axis.
:prelude (format nil "~@{~A~%~}" "set xtics nomirror" ;Get rid of mirrored tics on top.
"set link x2 via log10(2*pi)-x inverse log10(2*pi)-x" ;Map logarithm of x
"set x2tics" ;Label tics on top
(format nil "set x2label '~A'"
(cond (arbitrary-scaling "2pi/given") (scaling "lambda t") (t "lambda"))))
gnuplot-keys)))
;;Merge data into fewer bins to reduce fluctuations in graphs
;;Return new power struct
(defun rebin-power (power bins)
(let ((old-bins (length (power-data power))))
(if (= old-bins bins) power ;nothing to do
(let ((factor (floor old-bins bins))
(new (copy-power power)))
(setf (power-data new) (make-array bins) ;New arrays to use
(power-ks new) (make-array bins))
(unless (= (* bins factor) old-bins)
(error "New bin count ~D does not go evenly into old count ~D" bins old-bins))
(loop for bin below bins
do (loop repeat factor for old-index from (* bin factor)
for k = (aref (power-ks power) old-index)
sum (aref (power-data power) old-index) into total-data
when (plusp k)
sum k into total-k
and count t into k-count
do (setf (aref (power-data new) bin) (/ total-data factor)
(aref (power-ks new) bin) (if (zerop k-count) 0.0
(/ total-k k-count)))))
new))))
(defun plot-power-all (directory &rest keys)
(let ((title directory))
(setq directory (merge-pathnames directory batch-root-directory))
(apply #'plot-power directory :times (dump-times directory) :title title keys)))
(defun plot-power (directory
&rest keys
&key
time ;A single time
times ;A list of times
&allow-other-keys
)
(when time (push time times)) ;Convert single time to list
(apply #'plot-power-1
(loop for time in times
collect (list directory time (format-float-reasonably time)))
keys))
;;Two-point function
(mirror-images
;;Return two-point function <a'(sigma+delta) . a'(sigma)> in an array
;;Slot j stores distance jL/2N.
(defun two-point-function-a (diamond &optional extend-k)
(let ((l (loop-length diamond))
(power (loop-power-a diamond extend-k))) ;Array of P = 2k|A(k)|^2
(two-point-function-1 power l (loop-total-a diamond))))
)
;;Compute two-point-function from power and total a' or b'
(defun two-point-function-1 (power l total-a)
(let ((data (make-array (length power) :element-type 'double-float))) ;Working array
(loop for n from 1 below (length power)
do (setf (aref data n) (* (aref power n) (/ l 4 pi n)))) ;Divide by 2k to get |A^2|
;;Do cosine transform.
;;The two-point function at distance x is (2pi/L) sum_{n=-infinity}^infinity |A(k_n)|^2 exp(i k_n x),
;;with k_n = 2 pi n/L. We can write it sum_{n=1}^infinity |A(k_n)|^2 2 cos(i k_n x) + A_0^2 and compute it
;;with cosft, if we first divide A_0^2 by 2.
(setf (aref data 0) (/ (expt (3vector-length total-a) 2) ;Zero mode
(* 4 pi) l)) ;Normalized half of others because there's only one, not +k/-k
;;Slot n of the input data stores P(k_n). So the desired phase is 2 pi n x/L,
;;while the phase in the cosine transform is pi k j/N.. Thus slot j of the output data stores distance x_j = jL/2N,
;;and distances range from 0 to L/2 as j goes from 0 to N.
(cosft data)
;;Now multiply by prefactor (2pi/L) and an extra factor 2 from 2 cos(..) above.
(let ((factor (/ (* 4 pi) l)))
(dotimes (j (length data))
(setf (aref data j) (* (aref data j) factor))))
data))
;;Compute two-point function for a single spacing by brute force without smoothing
(defun slow-two-point-function (hats sigmas delta)
(loop with n = (length hats)
with l = (aref sigmas (1- n))
with result = 0.0
with sigma1 = 0.0 ;Two positions separated by delta
with sigma2 = delta
with index1 = 0 ;Ranges of sigma where these positions are found
with index2 = (loop for i from 0
while (> sigma2 (aref sigmas i))
finally (return i))
for max1 = (aref sigmas index1) ;End of this range
for max2 = (aref sigmas index2)
for range1 = (- max1 sigma1) ;Remaining distance to end of range
for range2 = (- max2 sigma2)
when (< range1 range2) ;Range 1 will run out first
do
(incf result (* (3vector-dot (aref hats index1) (aref hats index2)) range1))
(incf index1) ;Advance left segment.
(when (= index1 n) ;Left index reached end of array, so finished
(return (/ result l)))
(setq sigma1 max1 ;Advance left position to end of range 1.
sigma2 (mod (+ max1 delta) l)) ;Advance right by same amount. Wrap if needed.
else do ;Range 2 will run out first