-
Notifications
You must be signed in to change notification settings - Fork 2
/
gravitational.lisp
2414 lines (2236 loc) · 121 KB
/
gravitational.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
;;;Gravitational radiation
;;Convention for function and variable names in this file:
;; wave = I or J (possibly rescaled)
;; ij = |I_perp|^2 and maybe Im(I_x I_y^*) and the same for J
;; spectrum = power spectrum (i.e., the angular power density for each harmonic) in a certain direction
;; power = the power in a given direction
;; total-spectrum, total-power = the above quantities integrated over solid angle
;; binned means the spectrum summed in a series of logarithmic bins in harmonic number
(in-package "CL-USER")
;;Check that sigmas are not erroneously dsigmas
(defun check-sigmas (sigmas)
(unless (loop for index from 1 below (length sigmas)
always (< (aref sigmas (1- index)) (aref sigmas index)))
(error "sigmas are not in order, so probably they are dsigmas by mistake")))
;;Check that dsigmas are not erroneously sigmas
(defun check-dsigmas (dsigmas)
(unless (loop for dsigma across dsigmas
always (<= dsigma 1.0))
(error "Some dsigmas are > 1, so probably they are sigmas by mistake")))
(defun total-sigma (dsigmas)
(loop for dsigma across dsigmas
sum dsigma))
;;Average of a and b. Of course they should be the same...
(defun average-total-sigma (a-dsigmas b-dsigmas)
(/ (+ (total-sigma a-dsigmas) (total-sigma b-dsigmas)) 2.0))
;;Compute gravitational radiation given arrays of a' (or b') and sigmas.
;;Return two arrays indexed by harmonic number k, with the k = 0 slot zero:
;; |I_perp^k|^2 and Im(I_x I_y^*), where x, y, and Omega form a righthand coordinate system and
;; I^(k)(Omega) = (1/L) int_0^L dv a'(v) exp(2 pi i k (v-a_z(v))/L)
;;See Fourier.tex
;;If you want them computed in the rest frame, go to the rest frame first
;;Omega is a unit vector pointing toward the observer. n is the number of modes that you want
(defun gravitational-wave (hats sigmas omega n)
(check-sigmas sigmas)
(multiple-value-bind (wavex wavey n) (gravitational-wave-1 hats sigmas omega n)
(multiple-value-bind (iperp ixy) (process-gravitational-wave wavex wavey n)
(values iperp ixy))))
;;Return array of loci = (sigma_i - Omega . a_i)/l
(defun gravitational-wave-locations (hats sigmas omega)
(loop with n = (length sigmas)
with l = (aref sigmas (1- n))
with locations = (make-array n :element-type 'double-float)
for index below n
for pos = (make-zero-3vector) then (prog1 (3vector+ pos (3vector-scale hat (- next-sigma sigma)))
(deallocate 3vectors pos))
for hat = (aref hats index)
for sigma double-float = 0.0 then next-sigma
for next-sigma double-float = (aref sigmas index)
do (setf (aref locations index) (/ (- sigma (3vector-dot omega pos)) l))
finally (return locations)))
;;Omega is the observation direction, direction1 and direction2 are the two perpendicular directions
;;Returns arrays of locations (phases) x_i and values f1_i and f2_i for non-uniform FFT
;; I_c = 1/(2 pi n i) a'_c / (1-Omega.a') [exp{-2 pi j (sigma_{i+1} - Omega.a_{i+1}) - exp{-2 pi j (sigma_i - Omega.a_i)}] for c=1,2
;; = 1/(2 pi n i) sum_i fc_i exp{-2 pi i n x_i}
(defun gravitational-wave-data (hats sigmas omega direction1 direction2)
(declare (simple-vector hats) (type (simple-array double-float (*)) sigmas))
(let* ((nn (length sigmas))
(loci (gravitational-wave-locations hats sigmas omega)) ;array of (sigma_i - Omega.a_i)
(fvali1 (make-array nn :element-type 'double-float :initial-element 0.0))
(fvali2 (make-array nn :element-type 'double-float :initial-element 0.0)))
(declare (type (simple-array double-float (*)) loci fvali1 fvali2) (fixnum nn))
;;Now f_i
(loop for index below nn
for next-index = (mod (1+ index) nn)
for hat = (aref hats index)
for denominator double-float = (/ (3vector-squared-length (3vector- hat omega)) 2) ;1-Omega.a'
for coefficient1 double-float = (/ (3vector-dot hat direction1) denominator) ;a'_1/(1-Omega.a')
for coefficient2 double-float = (/ (3vector-dot hat direction2) denominator)
do
(decf (aref fvali1 index) coefficient1) ;negatively in this slot
(incf (aref fvali1 next-index) coefficient1) ;positively in next slot
(decf (aref fvali2 index) coefficient2)
(incf (aref fvali2 next-index) coefficient2)
)
(values loci fvali1 fvali2)))
;;Gather the data and do the Fourier transform and return (2 pi i n) I_x and similarly I_y, in the format returned by REALFT,
;;and the valid number of frequencies n
(defun gravitational-wave-1 (hats sigmas omega n)
;;Number of modes for NUFFT to compute. It must be a power of 2.
;;Some toward the end will be wrong, so we compute twice as many as needed
;;Probably it could be a lot smaller.
(let* ((nufft-modes (* n 2))) ;Minimum to compute
(setq nufft-modes (expt 2 (ceiling (log nufft-modes 2))) ;Up to power of 2
n (/ nufft-modes 2)) ;Number we will actually return
(multiple-value-bind (unitx unity) (two-perpendicular-vectors omega)
(multiple-value-bind (loc fvalx fvaly)
(gravitational-wave-data hats sigmas omega unitx unity)
(values (nufft loc fvalx *nufft-m* (* nufft-modes 2))
(nufft loc fvaly *nufft-m* (* nufft-modes 2))
n)))))
;;Process data from gravitational-wave-1, return |I_perp^k|^2 and Im(I_x I_y^*). The argument n is the number of valid
;;frequencies. The arrays may be larger.
(defun process-gravitational-wave (wavex wavey n)
(declare (type (simple-array double-float (*)) wavex wavey))
(let ((perp2spec (make-array n :element-type 'double-float))
(xyspec (make-array n :element-type 'double-float)))
(setf (aref perp2spec 0) 0.0
(aref xyspec 0) 0.0)
(loop for j from 1 below n
for jj = (* 2 j)
do (setf (aref perp2spec j) ;find Fourier spectra for |I_perp|^2 (or J)
(/ (+ (expt (aref wavex jj) 2) (expt (aref wavex (1+ jj)) 2)
(expt (aref wavey jj) 2) (expt (aref wavey (1+ jj)) 2))
4 pi pi j j)
;;find Fourier spectra for Im(I_xI_y^*) (or J)
(aref xyspec j)
(/ (- (* (aref wavex jj) (aref wavey (1+ jj)))
(* (aref wavex (1+ jj)) (aref wavey jj))) 4 pi pi j j)))
(values perp2spec xyspec)))
;;Compute one |I_perp|^2 or |J_perp|^2 by brute force using complex 3-vectors.
(defun slow-gravitational-wave (hats sigmas omega k)
(check-sigmas sigmas)
(let ((result (make-array 3 :initial-element 0.0)))
(loop with count = (length sigmas)
with l = (aref sigmas (1- count)) ;Length of loop
with expt = (* (complex 0 -1) (/ (* 2 pi k) l))
for index below count
for sigma = 0.0 then next-sigma ;Start of segment
for pos = zero-3vector then next-pos ;Start of segment
for next-sigma = (aref sigmas index) ;end of segment
for hat = (aref hats index) ;direction of segment
for next-pos = (3vector+ pos (3vector-scale hat (- next-sigma sigma))) ;end of segment
for scale = (/ (- (exp (* expt (- next-sigma (3vector-dot omega next-pos))))
(exp (* expt (- sigma (3vector-dot omega pos)))))
(- 1 (3vector-dot omega hat)))
do (dotimes (coordinate 3)
(incf (aref result coordinate) (* (aref hat coordinate) scale)))
finally
(unless (< (3vector-length next-pos) (* l 1d-10))
(warn "This loop does not appear to be in the rest frame. Delta = ~F" (3vector-length next-pos)))
(return ;|I_perp|^2 = |I|^2 - |I.Omega|^2
(/ (- (loop for coordinate below 3
sum (expt (abs (aref result coordinate)) 2))
(expt (abs (loop for coordinate below 3
sum (* (aref result coordinate) (aref omega coordinate))))
2))
4 pi pi k k)))))
;;Number of bins for N harmonics
(defun bin-count (n bin-size)
(round (real-log n bin-size))) ;Full round so that we get remainder also
(declaim (inline bin-start-harmonic bin-end-harmonic))
;;First harmonic in bin. If the bin edge falls between integers, take the next larger ones. But if
;;it is a just a tiny amount above an integer, use that integer. This avoids problems with, e.g,
;;(expt (expt 2.0 1/8) 8) = 2 + 4e-16.
(defun bin-start-harmonic (bin-size bin)
(declare (optimize speed)
(type (double-float 0.0) bin-size)
(type (signed-byte 32) bin)) ;Makes expt faster
(let ((result (fixnum-ceiling (* (expt bin-size bin) (- 1 1e-14)))))
(without-compiler-notes result))) ;Avoid warning about consing float when not inline
;;First harmonic beyond bin end
(defun bin-end-harmonic (bin-size bin)
(bin-start-harmonic bin-size (1+ bin)))
;;Number of harmonics in bin
(defun bin-harmonic-count (bin-size bin)
(- (bin-end-harmonic bin-size bin) (bin-start-harmonic bin-size bin)))
(declaim (inline harmonic-number-approximate sum-cos-n-small-x sum-cos-n-large-xn sum-cos-n))
(without-compiler-notes-return
;;Approximate H_n. = sum_1^n (1/n) The error is less than 10^-8 for n>10.
(defun harmonic-number-approximate (n)
(declare (optimize speed)
(type (unsigned-byte 62) n))
(let* ((nf (double-float n))
(result (+ (log nf) euler-constant (/ 1 2 nf) (/ -1 12 (expt nf 2)) (/ 1 120 (expt nf 4)))))
result)))
(without-compiler-notes-return
;;Calculate sum_{k=n_1}^infty cos(2 pi n x)/n in case where x is small using Euler-Maclaurin. See Lerch.tex.
(defun sum-cos-n-small-x (x n)
(declare (optimize speed)
(double-float x)
(fixnum n))
(let* ((nn (double-float n))
(phase (* 2 pi x nn)))
(+ (- (ci phase)) ;cos integral
(/ (+ (* (cos phase) (/ (+ 1 (/ 1 6 nn)) 2)) ;cos/2n + cos/12n^2 + sin/6n
(without-compiler-notes ;Avoid complaint involving reciprocal 6
(/ (* (sin phase) pi x) 6)))
nn)))))
(without-compiler-notes-return
;;Calculate sum_{k=n_1}^infty cos(2 pi n x)/n in case where x n is large using asymptotic
;;series for Lerch Phi in large n. See Lerch.tex.
(defun sum-cos-n-large-xn (x n)
(declare (optimize speed)
(double-float x)
(fixnum n))
(let* ((nn (double-float n))
(phase1 (* 2 pi x))
(cos1 (cos phase1))
(cosn (cos (* phase1 nn)))
(cosn-1 (cos (* phase1 (1- nn))))
(cosn+1 (cos (* phase1 (1+ nn))))
(d (* 2 (- 1.0 cos1) nn)))
(/ (- (- cosn cosn-1)
(/ (- (+ cosn+1 cosn-1) (* 2 cosn)) d)
)
d))))
;;Return sum_{k=n_1}^infty cos(2 pi n x)/n
;;except that if x=0 instead of the divergent sum_{k=n_1}^infty 1/n, return -sum_{k=1}^{1-n} 1/n
;;Thus (- (sum-cos-n-0 n1) (sum-cos-n-0 n2)) = sum_{k=n_1}^{n_2-1} 1/n regardless
;;See Lerch.tex
(defun sum-cos-n (x n)
(declare (type fixnum-size-float x) (fixnum n))
(setq x (mod x 1)) ;Mod out 2pi in phase
(when (> x 0.5) (setq x (- 1.0 x))) ;-x and x are the same.
(cond ((zerop x)
(- (harmonic-number-approximate (1- n))))
((< (* n (expt x 3)) 0.18)
(sum-cos-n-small-x x n))
(t (sum-cos-n-large-xn x n))))
(defun test-sum-cos-n (x n1 n2)
(let ((result (- (sum-cos-n x n1) (sum-cos-n x n2)))
(direct (loop for n from n1 below n2
sum (/ (cos (* 2 pi n x)) n))))
(format t "sum-cos-n ~S ~D ~D: our code: ~S direct sum: ~S, diff ~S~%" x n1 n2 result direct (- result direct))
result))
;;Increasing this above 1 causes more pairs of a'/b' values to be included when computing high frequencies.
(defparameter sum-cos-n-threshold-factor 1)
;;Suppose n >> 1, n even, and x = 0.5. Then (sum-cos-n x n infinity) = 1/n - 1/(n+1) + 1/(n+2) - ...
;;= 1/(n(n+1)) + 1/((n+2)(n+3) + ... = int_n^infty dk 1/(2k^2) = 1/(2n) approximately.
;;For x<<1, the individual cosines are close together, so we can approximate by int_n^infty dk cos(2 pi n x)/k
;;= -Ci(2 pi n x), whose magnitude is at most 1/(2 pi n x). The values of sum-cos-n oscillate, but the envelope
;;gradually declines. If there are N elements, and there will be N^2 contributions to the power, but their
;;signs are random, so we expect about N/(2 pi n x) in total. Thus we will not consider contributions from
;;x > N/(2 pi n), because their total contribution will not be significant.
(defun sum-cos-n-threshold (element-count n)
(min 0.5 (/ (* element-count sum-cos-n-threshold-factor) (* 2 pi n))))
;;Compute spectrum of a loop in a given direction
(defun binned-gravitational-spectrum (a-hats a-sigmas b-hats b-sigmas omega direct-n &key (total-n direct-n) (bin-size 2.0))
(mirror-images (check-sigmas a-sigmas))
(multiple-value-bind (bins error) (bin-count total-n bin-size) ;Total number of bins
(unless (< (abs error) 1e-12) (error "Bin size ~S did not go evenly into total frequencies ~D" bin-size total-n))
(multiple-value-bind (iperp ixy) (gravitational-wave a-hats a-sigmas omega direct-n)
(setq direct-n (length iperp)) ;Actual number directly computed
(multiple-value-bind (direct-bins error) (bin-count direct-n bin-size) ;Number of bins directly computed
(unless (< (abs error) 1e-12) (error "Bin size ~S did not go evenly into directly computed frequencies ~D"
bin-size direct-n))
(multiple-value-bind (jperp jxy) (gravitational-wave b-hats b-sigmas omega direct-n)
(let* ((spectrum (spectrum-from-ij iperp ixy jperp jxy)) ;spectrum through direct-n
(power (bin-gravitational-spectrum spectrum total-n bin-size))) ;Bin low-f data
(when (> total-n direct-n) ;Extension?
(let ((iperp-extension (binned-perp-direct a-hats a-sigmas omega direct-bins bins bin-size))
(jperp-extension (binned-perp-direct b-hats b-sigmas omega direct-bins bins bin-size)))
;;Extend power spectrum using I and J computed individually from a' and b'
;;iperp-extension has the sum of n |I_perp|^2. To get the average we should divide by
;;of the number of harmonics in the bin. Then we multiply by jperp-extension to get the
;;average of n^2 |I_perp|^2 |J_perp|^2. To get the sum we multiply by the number of harmonics,
;;but only once, so we must divide once.
(loop for bin from direct-bins below bins
do (setf (aref power bin)
(/ (* 8 pi (* (aref iperp-extension bin) (aref jperp-extension bin)))
(bin-harmonic-count bin-size bin))))))
power))))))
(defun test-binned-gravitational-spectrum (a-hats a-sigmas b-hats b-sigmas omega direct-n
&key (total-n direct-n) (bin-size 2.0))
(let ((start-time (get-internal-run-time))
(power (binned-gravitational-spectrum a-hats a-sigmas b-hats b-sigmas omega direct-n
:total-n total-n :bin-size bin-size))
(extended-time (get-internal-run-time))
(direct (binned-gravitational-spectrum a-hats a-sigmas b-hats b-sigmas omega total-n :bin-size bin-size))
(end-time (get-internal-run-time)))
(format t "~D a', ~D b', ~S seconds extended, ~S seconds direct" (length a-sigmas) (length b-sigmas)
(/ (double-float (- extended-time start-time)) internal-time-units-per-second)
(/ (double-float (- end-time extended-time)) internal-time-units-per-second))
(gnuplot 3 (length power)
#'(lambda (plot point)
(if (eq point :title) (nth plot '("directly computed" "extended" "abs error"))
(ecase plot
(0 (values (* (log bin-size 2.0) point) (aref direct point)))
(1 (values (* (log bin-size 2.0) point) (aref power point)))
(2 (and (> (expt bin-size point) direct-n)
(values (* (log bin-size 2.0) point)
(abs (- (aref power point) (aref direct point)))))))))
:logscale :y :styles :lines
:prelude (format nil "set format '%h'~%")
)))
;;Return an array of (phase coefficient1 coefficient2)
;;I^k = sum (coefficient1 direction1 + coefficient2 direction2) / (2 pi i k) exp(-2 pi i k phase)
;;returned phases are in order.
(defun gravitational-wave-elements (hats sigmas omega direction1 direction2)
(loop with n = (length sigmas)
with result = (make-array (* n 2))
with locations = (gravitational-wave-locations hats sigmas omega)
for index below n
for next-index = (1+ index)
for hat = (aref hats index)
for denominator double-float = (/ (3vector-squared-length (3vector- hat omega)) 2)
for coefficient1 double-float = (/ (3vector-dot hat direction1) denominator)
for coefficient2 double-float = (/ (3vector-dot hat direction2) denominator)
do (setf (aref result (1+ (* index 2))) ;Slot 1 has first loc when it is this phase. N-1 has last loc
(list (aref locations index) ;phase at start of segment
(- coefficient1) (- coefficient2))) ;negatively for this phase
if (< next-index n) ;Normal case
do (setf (aref result (+ 2 (* index 2))) ;Even slots have next phase data
(list (aref locations next-index) ;phase at end of segment
coefficient1 coefficient2)) ;positively for this phase
else do (setf (aref result 0) ;Slot 0 has next-phase data for last location
(list (aref locations 0)
coefficient1 coefficient2))
finally (return result)))
;;Account spectral contribution of a single pair of I_perp elements
;;Returns T if we did anything, NIL if first threshold was not satisfied.
(defun binned-perp-direct-1 (loc fvalx fvaly index1 index2 thresholds result first-bin bins bin-size wrap)
(declare (optimize speed)
(type (simple-array double-float (*)) loc fvalx fvaly thresholds result)
(float bin-size)
(fixnum index1 index2 first-bin bins))
(let ((phase (- (aref loc index2) (aref loc index1)))) ;relative phase
(when wrap ;We have wrapped. x2 < x1
(setq phase (+ phase 1))) ;mod into 0...1
(loop for bin from first-bin below bins
for threshold double-float = (aref thresholds bin) ;Max value of phase to use in this bin
until (and (>= phase threshold) ;Stop if phase is larger than threshold
(or wrap (> phase threshold))) ;Probably silly, but prevents duplication if phase=0.5 exactly
for this-sum-cos double-float = (sum-cos-n phase (bin-start-harmonic bin-size first-bin)) then next-sum-cos ;Sum from start of this bin
for next-sum-cos double-float = (sum-cos-n phase (bin-end-harmonic bin-size bin)) ;Sum from end of this bin
do (incf (aref result bin) (* 2 (+ (* (aref fvalx index1) (aref fvalx index2)) (* (aref fvaly index1) (aref fvaly index2)))
(- this-sum-cos next-sum-cos)))
;;If we exited on the first test of threshold, then bin = first-bin. Otherwise it has been incremented (even if there's only one bin)
finally (return (> bin first-bin)))))
;;Special case for element interacting with itself
(defun binned-perp-direct-1-self (fvalx fvaly index result first-bin bins bin-size)
(declare (optimize speed)
(type (simple-array double-float (*)) fvalx fvaly result)
(float bin-size)
(fixnum index first-bin bins))
(loop for bin from first-bin below bins
for this-sum-cos double-float = (sum-cos-n 0.0 (bin-start-harmonic bin-size first-bin)) then next-sum-cos ;Sum from start of this bin
for next-sum-cos double-float = (sum-cos-n 0.0 (bin-end-harmonic bin-size bin)) ;Sum from end of this bin
do (incf (aref result bin) (* (+ (expt (aref fvalx index) 2) (expt (aref fvaly index) 2))
(- this-sum-cos next-sum-cos)))
))
;;Return the sum of n |I_perp|^2 (or n |J_perp|^2) in each bin
(defun binned-perp-direct (hats sigmas omega first-bin bins bin-size)
(multiple-value-bind (direction1 direction2) (two-perpendicular-vectors omega)
(multiple-value-bind (loc fvalx fvaly)
(gravitational-wave-data hats sigmas omega direction1 direction2)
(let ((result (make-array bins :element-type 'double-float))
(thresholds (make-array bins :element-type 'double-float))
(count (length loc)))
(loop for bin from first-bin below bins
do (setf (aref thresholds bin) (sum-cos-n-threshold count (bin-start-harmonic bin-size bin))))
(loop for index1 below (length loc)
do (binned-perp-direct-1-self fvalx fvaly index1 result first-bin bins bin-size) ;Self term
do (loop for index2 fixnum from (1+ index1) below (length loc) ;Start with next element
;;Account this pair of elements. If phase the difference too large for even first threshold, stop scanning.
while (binned-perp-direct-1 loc fvalx fvaly index1 index2 thresholds result first-bin bins bin-size nil))
;;Now do elements that have wrapped, i.e., they are still after our location (mod 1) by less than threshold.
do (loop for index2 fixnum from 0
while (binned-perp-direct-1 loc fvalx fvaly index1 index2 thresholds result first-bin bins bin-size t)))
(loop for bin from first-bin below bins
when (minusp (aref result bin)) do (warn "Negative result in ~S" 'binned-perp-direct)
do (setf (aref result bin) (/ (aref result bin) 4 pi pi)))
result))))
;;Compute spectrum from I_perp, Im(I_x I_y^*), and the same for J.
(defun spectrum-from-ij (iperp ixy jperp jxy)
;;To avoid aliasing, it may be necessary to compute more frequencies in, say, A, than will ever be used because
;;they are strongly suppressed in B.
(let* ((frequencies (min (length iperp) (length jperp)))
(spectrum (make-array frequencies :element-type 'double-float)))
(setf (aref spectrum 0) 0.0)
(loop for harmonic from 1 below frequencies
do (setf (aref spectrum harmonic)
(* 8 pi (expt harmonic 2)
(+ (* (aref iperp harmonic) (aref jperp harmonic))
(* 4 (aref ixy harmonic) (aref jxy harmonic))
))))
spectrum))
;;Separate directly computed power into bins. Bin-size must go evenly into n.
(defun bin-gravitational-spectrum (power n bin-size)
(declare (type (simple-array double-float (*)) power)
(double-float bin-size)
(fixnum n))
(let* ((count (length power))
(bins (round (bin-count n bin-size)))
(data (make-array bins :element-type 'double-float :initial-element 0.0)))
(locally (declare (optimize speed))
(loop for i from 1 below count
do (incf (aref data (fixnum-floor (/ (real-log (double-float i)) (real-log bin-size))))
(aref power i))))
data))
;;Power in gravitational waves from a loop in a given direction, in units of G mu^2
(defun gravitational-power (a-hats a-sigmas b-hats b-sigmas omega direct-n &key (total-n direct-n) (bin-size 2.0))
(let ((spectrum (binned-gravitational-spectrum a-hats a-sigmas b-hats b-sigmas omega direct-n :total-n total-n :bin-size bin-size)))
(loop for k below (length spectrum)
sum (aref spectrum k))))
#|
;;Wrong calculation!
(defun slow-gravitational-wave-power (diamond omega k)
(multiple-value-bind (a-hats a-sigmas) (get-a-data-sigmas diamond)
(multiple-value-bind (b-hats b-sigmas) (get-b-data-dsigmas diamond)
(let ((i (slow-gravitational-wave-a a-hats a-sigmas omega k)) ;I_perp^2
(j (slow-gravitational-wave-b b-hats b-sigmas omega k))) ;J_perp^2
(* (* 8 pi) k k i j)))))
|#
;;Total gravitational power emitted by a loop
(defun total-gravitational-power (a-hats a-sigmas b-hats b-sigmas direct-n &key (total-n direct-n) (bin-size 2.0) (split-levels 0))
(spherical-integral
#'(lambda (omega) (gravitational-power a-hats a-sigmas b-hats b-sigmas omega direct-n :total-n total-n :bin-size bin-size))
split-levels))
;;Returns total power and momentum in gravitational waves as a 4vector
(defun gravitational-momentum-4vector (a-hats a-sigmas b-hats b-sigmas direct-n
&key (total-n direct-n) (bin-size 2.0) (split-levels 0))
(let ((result (make-zero-4vector)))
(flet ((integrate-power (omega area)
(let ((power (* area ;Power in this direction
(gravitational-power a-hats a-sigmas b-hats b-sigmas omega
direct-n :total-n total-n :bin-size bin-size))))
(4vector-incf result (4vector-scale (3to4vector omega 1.0) power)))))
(spherical-integral-1 #'integrate-power split-levels)
result)))
;;Backward compatibility: energy and magnitude of momentum as separate values
(defun gravitational-energy-momentum (&rest args)
(let ((momentum (apply #'gravitational-momentum-4vector args)))
(values (4vector-t momentum) (3vector-length momentum))))
;;Binned 4-momentum spectrum integrated over directions
(defun total-gravitational-4vector-spectrum (a-hats a-sigmas b-hats b-sigmas direct-n
&key (total-n direct-n) (bin-size 2.0) (split-levels 0))
(let* ((bins (bin-count total-n bin-size))
(momentum (coerce (loop repeat bins collect (make-zero-4vector)) 'vector)))
(flet ((integrate-momentum (omega area)
(loop with spectrum = (binned-gravitational-spectrum a-hats a-sigmas b-hats b-sigmas omega direct-n
:total-n total-n :bin-size bin-size)
for k below bins
do (4vector-incf (aref momentum k)
(4vector-scale (3to4vector omega 1.0)
(* (aref spectrum k) area))))))
(spherical-integral-1 #'integrate-momentum split-levels)
momentum)))
;;Just energy
(defun total-gravitational-spectrum (&rest args)
(map 'vector #'(lambda (x) (4vector-t x)) (apply #'total-gravitational-4vector-spectrum args)))
;;Spherical integration
;;Do a spherical integral by averaging faces in in a triangulation.
(defun spherical-integral (function split-levels)
(let ((result 0.0))
(spherical-integral-1 #'(lambda (center area) ;Add up area times function value at center
(incf result (* area (funcall function center))))
split-levels)
result))
;;Call function with center and area for each triangle
(defun spherical-integral-1 (function split-levels &optional (triangles (triangulate-sphere split-levels)))
(format t "Total of ~D triangles: " (length triangles)) (force-output)
(loop for triangle across triangles
for count from 1
for center = (triangle-center triangle)
for area = (apply #'spherical-triangle-area triangle)
do (funcall function center area)
when (zerop (mod count 10)) do (format t "~D " count) (force-output))
(terpri))
;;Adaptive spherical integral, following Boal and Sayas, "Adaptive Numerical Integration on Spherical Triangles"
(defstruct (spherical-integration-element
(:conc-name SI-))
a b c ;Outer triangle
ab bc ca ;Midpoints of segments
i0 ;Integral over outer triangle
ia ib ic icenter ;Integrals over 4 subtriangles
level ;Split level of smaller triangles
cost ;Cost of doing the 4 integrations
)
;;Estimated (absolute) error due to this triangle is 4/3 (see Boal and Sayas) times the difference between using the 4
;;subtriangles and using only the full triangle.
;;We could make an approximation by just comparing the outer subtriangles to the center, but when we split we already have the
;;outer triangle integral, so we don't need to recompute it.
(defun si-error (si)
(abs (* 4/3 (- (si-value si) (si-i0 si)))))
(defun si-error-larger-p (si1 si2)
(> (si-error si1) (si-error si2)))
;;See if si1 is the better candidate to split than si2
(defun si-better-split-p (si1 si2)
(> (/ (si-error si1) (si-cost si1))
(/ (si-error si2) (si-cost si2))))
;;Contribution to integral from this element
(defun si-value (si)
(+ (si-ia si) (si-ib si) (si-ic si) (si-icenter si)))
(defvar my-heap)
(defvar my-si)
(defun adaptive-spherical-integral (function initial-split-levels tolerance max-recursion)
(let* ((heap (setup-initial-si function initial-split-levels))
(integral 0.0)
(error 0.0)
(cost 0.0))
(setq my-heap heap)
(heap-map heap #'(lambda (si)
(incf integral (si-value si))
(incf error (si-error si))
(incf cost (si-cost si))))
(loop with last-count = 0
with si
when (>= (heap-count heap) (* 2 last-count))
do (format t "~&Error ~S with ~D samples and cost ~S~%" error (* 4 (setq last-count (heap-count heap))) cost)
when (<= error (* tolerance (abs integral))) return t
do (setq si (heap-remove heap)) ;Triangle with largest error
when (= (si-level si) max-recursion)
do (setq my-si si)
and do (error "Max recursion ~D reached without achieving requested tolerance ~S. Value ~S, error estimate ~S"
max-recursion tolerance integral error)
do (decf integral (si-value si)) ;Remove from result
do (decf error (si-error si))
do (decf cost (si-cost si))
do (dolist (sub (si-split si heap function)) ;Split into 4 SI's
(incf integral (si-value sub)) ;Add them in to result
(incf error (si-error sub))
(incf cost (si-cost sub))))
(format t "Integration succeeded with ~D samples, error ~S, and cost ~S" (* 4 (heap-count heap)) error cost)
integral))
(defun setup-initial-si (function initial-split-levels)
(setq initial-split-levels (max 0 (1- initial-split-levels)))
(format t "~&Starting with ~D samples" (* 20 (expt 4 (1+ initial-split-levels))))
(loop with heap = (create-heap #'si-better-split-p)
for (a b c) across (triangulate-sphere initial-split-levels)
do (create-si heap function a b c)
finally (format t "initial scan done ~%")
(return heap)
))
;;Integrate one triangle by taking the midpoint.
(defun spherical-triangle-integrate (function a b c)
(multiple-value-bind (value cost) (funcall function (spherical-triangle-center a b c))
(unless cost (setq cost 1.0))
(values (* value (spherical-triangle-area a b c)) cost)))
;;Split SI object and put subobjects into the heap. Return list of them
(defun si-split (si heap function)
(list (create-si heap function (si-a si) (si-ab si) (si-ca si) (1+ (si-level si)) (si-ia si))
(create-si heap function (si-b si) (si-bc si) (si-ab si) (1+ (si-level si)) (si-ib si))
(create-si heap function (si-c si) (si-ca si) (si-bc si) (1+ (si-level si)) (si-ic si))
(create-si heap function (si-ab si) (si-bc si) (si-ca si) (1+ (si-level si)) (si-icenter si))))
;;Create an SI object, filling in the mid points and the sub-integrals. Put it in the heap.
(defun create-si (heap function a b c &optional (level 1) i0)
(let ((total-cost 0.0))
(flet ((integrate-with-cost (a b c)
(multiple-value-bind (value cost) (spherical-triangle-integrate function a b c)
(incf total-cost cost)
value)))
(let* ((ab (spherical-midpoint a b))
(bc (spherical-midpoint b c))
(ca (spherical-midpoint c a))
(si (make-spherical-integration-element
:a a :b b :c c
:ab ab :bc bc :ca ca
:ia (integrate-with-cost a ab ca)
:ib (integrate-with-cost b bc ab)
:ic (integrate-with-cost c ca bc)
:icenter (integrate-with-cost ab bc ca)
:level level
:i0 (or i0 (spherical-triangle-integrate function a b c))) ;i0 not included in cost
))
(setf (si-cost si) total-cost)
(heap-insert heap si)))))
;;Triangulate sphere and split as in triangulate-sphere-separate-cusps
;;Call FUNCTION with triangle and a list of cusp-info
;;You have to add up the results yourself
(defun spherical-integral-separate-cusps (function split-levels cusp-data threshold1 threshold2)
(let ((data (triangulate-sphere-separate-cusps split-levels cusp-data threshold1 threshold2)))
(loop for (triangle . list) across data
count list into cusp-triangles
sum (length list) into total
finally (format t "Cusps: ~D, Total triangles: ~D, triangles near cusps: ~D~@[, cusps/triangle ~S~]~%"
(length cusp-data) (length data) cusp-triangles
(and (plusp cusp-triangles) (/ (double-float total) cusp-triangles))))
(loop for (triangle . list) across data
for count from 1
do (funcall function triangle list)
when (zerop (mod count 10)) do (format t "~D " count) (force-output))))
;;Plots a set of triangles
;;If separate given, push each triangle in the direction of its center by this amount to separate them.
(defun plot-triangles (faces &rest gnuplot-keys &key (separate 0.0))
(apply #'gnuplot 1 (* (length faces) 6)
#'(lambda (plot point)
(declare (ignore plot))
(unless (eq point :title)
(multiple-value-bind (triangle corner) (floor point 6)
(and (< corner 4) ;double NIL to break between surfaces
(let ((face (aref faces triangle)))
(values-list (3vector-list
(3vector+ (nth (if (= corner 3) 0 corner) face)
(3vector-scale (triangle-center face) separate)))))))))
:3d t
:styles :lines
gnuplot-keys))
;;Plot triangles showing which involve cusps
(defun test-triangulate-sphere-separate-cusps (levels cusp-data threshold1 threshold2
&rest gnuplot-keys &key no-far &allow-other-keys)
(let ((triangles (triangulate-sphere-separate-cusps levels cusp-data threshold1 threshold2)))
(apply #'gnuplot (1+ (* 2 (length cusp-data))) ;0 = far, 1 = first circle, 2 = first triangles, ...
(max 200 (* 6 (length triangles)))
#'(lambda (plot point)
(let ((direction (and (plusp plot) (cusp-info-direction (nth (floor (1- plot) 2) cusp-data)))))
(if (eq point :title)
(if (zerop plot) (unless no-far "far")
(format nil "~$,~$,~$" (3vector-x direction) (3vector-y direction) (3vector-z direction)))
(if (evenp plot) ;actual triangle set
(unless (and (zerop plot) no-far)
(multiple-value-bind (position corner) (floor point 6)
(and (< position (length triangles))
(< corner 4) ;double NIL to break between surfaces
(let ((data (aref triangles position))) ;(triangles . directions)
(and (if (zerop plot) (null (cdr data)) ;Triangles with no cusp
;;includes this direction?
(member direction (cdr data) :key #'cusp-info-direction))
(values-list (3vector-list (nth (if (= corner 3) 0 corner)
(car data)))))))))
(and (< point 200)
(let ((angle (* point (/ pi 100)))) ;0..2pi
(multiple-value-bind (x y) (two-perpendicular-vectors direction)
(values-list (3vector-list (3vector+ (3vector-scale direction (cos threshold1))
(3vector-scale x (* (sin threshold1) (cos angle)))
(3vector-scale y (* (sin threshold1) (sin angle))) ))))))))))
:3d t
:styles :lines
gnuplot-keys)))
;;Plotting of gravitational radiation
;;Gravitational spectra plotting
(defun plot-total-gravitational-spectrum (a-hats a-sigmas b-hats b-sigmas direct-n &rest keys &key (bin-size 2.0) &allow-other-keys)
(apply #'plot-binned-gravitational-spectrum
(apply #'total-gravitational-spectrum a-hats a-sigmas b-hats b-sigmas direct-n keys)
bin-size
keys))
;;Plot binned spectrum
(defun plot-binned-gravitational-spectrum (power bin-size &rest keys)
(apply #'gnuplot 1 (length power)
#'(lambda (plot point)
(declare (ignore plot))
(and (numberp point)
(values (expt bin-size point) (aref power point))))
:logscale '(:x :y)
:prelude (format nil "set format '%h'~%")
keys))
;;Plot projected angular distribution of something
;;There is one point at the north pole, one at the south pole, and STEPS values of phi for each
;;of the STEPS intermediate values of theta
;;It would be better the colors corresponded to the the power at the center of the region that is colored,
;;instead of the average of the corners.
;;We could also do it by inverse mapping
(defun plot-projected-angular-distribution (function steps
&rest gnuplot-keys
&key cbmax ;Value at which to saturate the color scale.
(colorbox t) ;if NIL, do not show it
&allow-other-keys)
(let ((npoints (+ 3 (* steps (1+ steps)))) ;2 polar points, s^2 regular points, 1+s breaks
(dtheta (/ pi (1+ steps)))
(dphi (/ (* 2 pi) steps)))
(apply #'gnuplot 1 npoints
#'(lambda (plot point)
(declare (ignore plot))
(when (numberp point)
(let ((x nil) y z)
(cond ((zerop point) (setq x 0.0 y 0.0 z 1.0))
((= point 1)) ;First break
((= point (1- npoints)) (setq x 0.0 y 0.0 z -1.0))
(t (multiple-value-bind (theta-step phi-step) (floor (- point 2) (1+ steps))
(unless (= phi-step steps) ;break
(let* ((theta (* (1+ theta-step) dtheta))
(phi (* (- phi-step (/ (1- steps) 2.0)) dphi)))
(setq x (* (sin theta) (cos phi))
y (* (sin theta) (sin phi))
z (cos theta)))))))
(and x
(multiple-value-call
#'values (Mollweide x y z)
(values (funcall function (make-3vector x y z)))
)))))
:prelude (format nil "~@[set cbrange [0:~S]~]
set view 0,0,1.8,1.8~%set pm3d ftriangles~%unset border~%unset tics~%set cbtics
unset lmargin~%unset rmargin~%unset tmargin~%unset bmargin
~:[unset colorbox~;set colorbox horizontal user origin 0.01,0.95 size 0.2, 0.05~]
"
cbmax colorbox)
:styles :pm3d
:3d t :key nil
gnuplot-keys)))
;;Overlay tangent vectors on distribution
;;Many features are not included
(defun plot-projected-angular-distribution-and-tangent-vectors (function steps data
&rest gnuplot-keys
&key styles
cbmax ;Value at which to saturate the color scale.
(colorbox t) ;if NIL, do not show it
&allow-other-keys)
(setq data (coerce data 'vector))
(unless styles
(setq gnuplot-keys (list* :styles
(append (list :pm3d)
(loop for (vectors title style) across data collect style))
gnuplot-keys)))
(let ((npoints (+ 3 (* steps (1+ steps)))) ;2 polar points, s^2 regular points, 1+s breaks
(dtheta (/ pi (1+ steps)))
(dphi (/ (* 2 pi) steps)))
(apply #'gnuplot (+ (length data) 1)
(max npoints (loop for (vectors) across data maximize (1+ (length vectors))))
#'(lambda (plot point)
(when (numberp point)
(if (zerop plot) ;distribution
(let ((x nil) y z)
(cond ((> point npoints)) ;Not part of distribution
((zerop point) (setq x 0.0 y 0.0 z 1.0))
((= point 1)) ;First break
((= point (1- npoints)) (setq x 0.0 y 0.0 z -1.0))
(t (multiple-value-bind (theta-step phi-step) (floor (- point 2) (1+ steps))
(unless (= phi-step steps) ;break
(let* ((theta (* (1+ theta-step) dtheta))
(phi (* (- phi-step (/ (1- steps) 2.0)) dphi)))
(setq x (* (sin theta) (cos phi))
y (* (sin theta) (sin phi))
z (cos theta)))))))
(and x
(multiple-value-call
#'values (Mollweide x y z)
(values (funcall function (make-3vector x y z)))
)))
;;tangent vectors
(let ((vector (car (aref data (1- plot)))))
(when vector
(when (= point (length vector))
(setq point 0))
(let ((p (and (< point (length vector)) (aref vector point))))
(and p
(multiple-value-bind (sx sy) (apply #'Mollweide (3vector-list p))
(values-list (list sx sy 0.0) ;Z coordinate
)))))))))
:prelude (format nil "~@[set cbrange [0:~S]~]
set view 0,0,1.8,1.8~%set pm3d ftriangles~%unset border~%unset tics~%set cbtics
unset lmargin~%unset rmargin~%unset tmargin~%unset bmargin
~:[unset colorbox~;set colorbox horizontal user origin 0.01,0.95 size 0.2, 0.05~]
"
cbmax colorbox)
:3d t :key nil
gnuplot-keys)))
;;Plot angular distribution of gravitational wave emission
(defun plot-gravitational-power (a-hats a-sigmas b-hats b-sigmas direct-n steps &rest keys &key (total-n direct-n) (bin-size 2.0)
&allow-other-keys)
(apply #'plot-projected-angular-distribution
#'(lambda (direction)
(gravitational-power a-hats a-sigmas b-hats b-sigmas direction direct-n
:total-n total-n :bin-size bin-size))
steps
keys))
(defun plot-gravitational-power-one-bin-and-tangent-vectors (a-hats a-dsigmas b-hats b-dsigmas direct-n steps bin
&rest keys &key (total-n direct-n) (bin-size 2.0)
(style :points) ;Style for a' and b'
interpolate
&allow-other-keys)
(mirror-image-let ((smooth-a-hats (if interpolate (interpolate-hats a-hats :split-count interpolate) a-hats)))
(apply #'plot-projected-angular-distribution-and-tangent-vectors
#'(lambda (direction)
(aref (binned-gravitational-spectrum a-hats (dsigma-to-sigma a-dsigmas)
b-hats (dsigma-to-sigma b-dsigmas)
direction direct-n
:total-n total-n :bin-size bin-size)
bin))
steps
(append (list (list a-hats "A" style)
(list b-hats "B" style))
(and interpolate
(list (list smooth-a-hats nil "lines lt 1")
(list smooth-b-hats nil "lines lt 2"))))
keys)))
(defun plot-ab-and-power (loop n-harmonics n-points &key reuse power-keys ab-keys)
(setq loop (rest-frame-loop loop))
(multiplot (:rows 2 :reuse reuse :prelude (format nil "set terminal x11 size 700,600~%"))
(multiplot-format "set origin 0.0,0.45~%")
(apply #'plot-gravitational-power loop n-harmonics n-points power-keys)
(multiplot-format "set size 1.0,0.45~%")
(apply #'plot-ab loop :rotate nil ab-keys)))
;;Different version
(defun plot-ab-and-power-1 (loop n-harmonics n-points
&rest gnuplot-keys &key (a-style "p lt 2") (b-style "p lt 4")
&allow-other-keys)
(setq loop (rest-frame-loop loop))
(mirror-image-let ((a-data (get-a-hats loop)))
(destructuring-bind (power-plots power-points power-func &key prelude &allow-other-keys)
(call-with-hats #'plot-gravitational-power loop n-harmonics n-points :return-args t)
(assert (= power-plots 1))
(apply #'gnuplot 3 ;power, a, b
(max (length a-data) (length b-data) power-points)
#'(lambda (plot point)
(cond ((eq point :title)
(nth plot '(nil "A" "B")))
((zerop plot) ;Power
(and (< point power-points)
(funcall power-func plot point)))
(t (let ((data (if (= plot 1) a-data b-data)))
(and (< point (length data))
(let ((p (aref data point)))
(multiple-value-bind (sx sy) (apply #'Mollweide (3vector-list p))
(values sx sy 0.0))))))))
:prelude prelude
:styles (list :pm3d a-style b-style)
:3d t
gnuplot-keys))))
;;Test function for sphere plot
(defun test-sphere (theta-steps &rest keys &key (phi-steps (* 2 theta-steps)) cbmax &allow-other-keys)
(apply #'gnuplot 1 (* (1+ theta-steps) (+ 2 phi-steps))
#'(lambda (plot point)
(declare (ignore plot))
(when (numberp point)
;;theta-step = 0...step
;;phi-step = 0...phi-steps + 1, to leave room for blank record
(multiple-value-bind (theta-step phi-step) (floor point (+ 2 phi-steps))
(if (> phi-step phi-steps) ;end of circle of fixed theta
nil
(let* ((theta (* theta-step (/ pi theta-steps)))
(phi (* phi-step (/ (* 2 pi) phi-steps)))
(direction (spherical-coordinates theta phi)))
(values-list
(append
(3vector-list direction)
;;This previous code doesn't work. Maybe there's something wrong with the choice of corners?
;; (list (+ (if (zerop phi-step) 0.0 1.0) (if (zerop theta-step) 0.0 1.0)))
(list (+ (/ phi-step (float phi-steps)) (/ theta-step (float theta-steps))))
)))))))
:3d t
:styles :pm3d
:prelude (format nil "~@[set cbrange [0:~S]~]~%set pm3d depthorder corners2color c2~%unset border
unset tics~%set cbtics~%unset lmargin~%unset rmargin~%unset tmargin~%unset bmargin~%" cbmax)
keys))
;;Batch processing
(defun compute-radiated-energy (loop-file direct-n &key total-n split-levels (output-file (merge-pathnames "energy-momentum.lisp" loop-file)))
(initialize)
(read-one-loop loop-file)
(with-group-write-access
(ensure-directories-exist output-file)
(with-open-file (stream output-file :direction :output :if-exists :supersede)
(multiple-value-bind (energy momentum)
(call-with-hats (rest-frame-loop (longest-loop))
#'gravitational-energy-momentum
direct-n :total-n total-n :split-levels split-levels)
(format stream ";;energy, momentum~%(~S ~S)~%" energy momentum)))))
;;Loop files are directory/N/loop-N.dat
(defun run-radiated-energy (directory direct-n &key total-n split-levels)
(load "load")
(loop for number in (with-open-file (stream (format nil "~A/list-of-loops.dat" directory)) (read stream))
for file = (format nil "~A/~D/loop-~D.dat" directory number number)
while (probe-file file)
for input-directory = (pathname-directory file)
for output-file = (merge-pathnames (format nil "energy-momentum-~D.lisp" number)
(make-pathname :directory (list :absolute "cluster" "tufts" "strings" "energy-momentum"
(nth (- (length input-directory) 2) input-directory))))
do (ensure-directories-exist output-file)
unless (probe-file output-file)
do (print number)
(do-submit (namestring (make-pathname :directory (pathname-directory output-file)))
(format nil "EM ~D" number)
(namestring (make-pathname :name (format nil "compute-EM-~D-" number) :type nil :defaults output-file))
batch-flags
`(compute-radiated-energy ,file ,direct-n :output-file ,(namestring output-file)
:total-n ,total-n :split-levels ,split-levels)
:load-file (namestring (merge-pathnames "strings/parallel/load.lisp" (user-homedir-pathname))))))
(defun plot-radiated-energy (directory)
(loop with slopes = '(0.1 0.2 0.3)
for file in (directory (format nil "~A/energy-momentum-*.lisp" directory))
for this = (with-open-file (stream file) (read stream nil))
when this
collect this into data
finally (let ((max (loop for (energy) in data maximize energy)))
(format t "~D loops ~%" (length data))
(gnuplot (1+ (length slopes)) (length data)
#'(lambda (plot point)
(cond ((eq point :title)
(if (zerop plot) "loops"
(format nil "rocket fraction ~S" (nth (1- plot) slopes))))
((zerop plot)
(values-list (nth point data)))
(t (case point
(0 (values 0.0 0.0))
(1 (values max (* max (nth (1- plot) slopes))))))))
:styles (cons :points (loop for entry in slopes collect :lines))
))))
;;Different version
(defun plot-radiated-energy-1 (directory)
(loop for file in (directory (format nil "~A/energy-momentum-*.lisp" directory))
for this = (with-open-file (stream file) (read stream nil))
when this
collect this into data
finally (format t "~D loops ~%" (length data))
(gnuplot 1 (length data)
#'(lambda (plot point)
(declare (ignore plot))
(if (eq point :title) "loops"
(destructuring-bind (energy momentum) (nth point data)
(values energy (/ momentum energy))))))))
(defun histogram-rocket-effect (directory &rest gnuplot-keys &key (bins 100) &allow-other-keys)
(let ((data (make-array bins :initial-element 0.0))
(list nil))
(loop for file in (directory (format nil "~A/energy-momentum-*.lisp" directory))
for (energy momentum) = (with-open-file (stream file) (read stream nil))
when energy
do (let ((rocket (/ momentum energy)))
(incf (aref data (floor (* rocket bins))))
(push rocket list)))
(setq list (sort list #'<))
(format t "~D loops, average rocket fraction ~S, median ~S"
(length list)
(/ (reduce #'+ list) (length list))
(nth (floor (length list) 2) list))
(apply #'gnuplot 1 (length data)
#'(lambda (plot point)
(declare (ignore plot))
(unless (eq point :title)
(values (/ (+ point 0.5) bins) (aref data point))))
:styles :boxes
:prelude (format nil "set style fill solid 0.7~%")
gnuplot-keys)))
(defun write-initial-rocket-fractions (directory)
(with-open-file (stream (format nil "~A/initial-rocket-fractions.dat" directory)
:direction :output :if-exists :supersede)
(loop for file in (directory (format nil "~A/energy-momentum-*.lisp" directory))
for (energy momentum) = (with-open-file (stream file) (read stream nil))
when energy
do (format stream "~S~%" (/ momentum energy)))))
;;Smooth strings stored as their Fourier transforms
;;In this section, a scalar function on 0..1 is stored as its Fourier transform in the format (but not the
;;normalization) returned by REALFT. This consists of an array of 2*Nf real numbers representing frequencies 0...Nf-1
;;and frequencies -Nf+1...-1, which are just conjugates of the positive frequencies.
;;The first element is the zero mode, which vanishes if the function averages to 0.
;;The second element, which would be freq Nf (called N/2 by Numerical Recipes) must be 0.
;;Frequency j=1...Nf-1 has its real part in slot 2j and its imaginary part in slot 2j+1.
;;The function f(x) = sum_{j=-Nf}^Nf F_j exp{-2 pi i j x} = sum_{j=1}^Nf 2 Re (F_j exp{-2 pi i j x})
;;The inverse discrete Fourier transform defined by Numerical Recipes yields 1/(2 Nf) f(x_j) where x = j/(2 Nf).