;;; various standard benchmarks 
;;; --------------------------------------------------------------------------------

(when (provided? 'pure-s7)
  (define (string-length str)
    (if (string? str)
	(length str)
	(if (openlet? str)
	    ((let-ref str 'string-length) str)
	    (error 'wrong-type-arg "string-length argument should be a string: ~A" str)))))

(set! (*s7* 'heap-size) 512000)

(define (fannkuch n)
  (call-with-exit
   (lambda (return)
     (let ((perm (make-int-vector n))
	   (perm1 (make-int-vector n))
	   (count (make-int-vector n))
	   (subs (make-vector n))
	   (subs1 (make-vector n))
	   (maxFlipsCount 0)
	   (checksum 0)
	   (r n)
	   (perm0 0))
       
       (do ((i 0 (+ i 1)))
	   ((= i n))
	 (int-vector-set! perm1 i i)
	 (vector-set! subs i (subvector perm 0 (+ i 1)))
	 (vector-set! subs1 i (subvector perm1 1 (+ i 1))))
       
       (do ((permCount #t (not permCount)))
	   (#f)
	 (do ()
	     ((= r 1))
	   (int-vector-set! count (- r 1) r)
	   (set! r (- r 1)))
	 (copy perm1 perm)
	 
	 (do ((flipsCount 0 (+ flipsCount 1))
	      (k (int-vector-ref perm 0) (int-vector-ref perm 0)))
	     ((= k 0)
	      (set! maxFlipsCount (max maxFlipsCount flipsCount))
	      (if permCount 
		  (set! checksum (+ checksum flipsCount)) 
		  (set! checksum (- checksum flipsCount))))
	   (reverse! (vector-ref subs k)))
	 
	 (do ((done #f))
	     (done)
	   (when (= r n)
	     (return (cons checksum maxFlipsCount)))
	   
	   (set! perm0 (int-vector-ref perm1 0))
	   (copy (vector-ref subs1 r) perm1)
	   (int-vector-set! perm1 r perm0)

	   (if (positive? (int-vector-set! count r (- (int-vector-ref count r) 1)))
	       (set! done #t)
	       (set! r (+ r 1)))))))))

;; (fannkuch 7): (228 . 16), 8: (1616 . 22), 9: (8629 . 30), 10: (73196 . 38), 11: (556355 . 51), 12: (3968050 . 65)
(fannkuch 8)
(display (fannkuch 8)) (newline)

;;; --------------------------------------------------------------------------------

(define (wc)
  (let ((nw 0)
	(nc 0)
	(port (open-input-file "/home/bil/test/scheme/bench/src/bib")))
    (do ((newk 0 0)
	 (str (read-line port) (read-line port))
	 (nl 0 (+ nl 1)))
	((eof-object? str)
	 (close-input-port port)
	 (list nl (+ nw nl) nc))
      (set! nc (+ nc 1 (string-length str)))
      (do ((k (char-position #\space str) (char-position #\space str (+ k 1))))
	  ((not k))
	(if (not (= k newk))
	    (set! nw (+ nw 1)))
	(set! newk (+ k 1))))))

(wc) (wc)
(display (wc)) (newline) ; '(31102 851820 4460056)

;;; --------------------------------------------------------------------------------

(define (cat)
  (let ((inport (open-input-file "/home/bil/cl/s7test.scm"))
	(outport (open-output-file "/dev/null"))) 
    (catch #t
      (lambda ()
	(do () (#f) 
	  (write-string (read-line inport #t) outport)))
      (lambda args
	(close-input-port inport)
	(close-output-port outport)))))

(do ((i 0 (+ i 1)))
    ((= i 12))
  (cat))

;;; --------------------------------------------------------------------------------

(define (string-cat n)
  (let ((s "abcdef"))
    (do ((i 0 (+ i 1)))
	((= i 100) 
	 (string-length s))
      (set! s "abcdef")
      (do ()
	  ((> (string-length s) n))
	(set! s (string-append "123" s "456" s "789"))
	(set! s (string-append
		 (substring s (quotient (string-length s) 2))
		 (substring s 0 (+ 1 (quotient (string-length s) 2)))))))))

(display (string-cat 500000)) (newline) ; 524278

;;; --------------------------------------------------------------------------------

(define mbrot
  (let ()
    (define (count cr ci)
      (let ((max-count 64)
	    (radius^2 16.0)
	    (tzr 0.0)
	    (zr cr)
	    (zi ci))
	(do ((zr^2 (* zr zr) (* zr zr))
	     (zi^2 (* zi zi) (* zi zi))
	     (c 0 (+ c 1)))
	    ((or (= c max-count)
		 (> (+ zr^2 zi^2) radius^2))
	     c)
	  (set! tzr (+ (- zr^2 zi^2) cr))
	  (set! zi (+ (* 2.0 zr zi) ci))
	  (set! zr tzr))))

    (define (mb matrix r i step n)
      (do ((x 0 (+ x 1)))
	  ((= x n))
	(do ((y 0 (+ y 1)))
	    ((= y n))
	  (vector-set! matrix x y (count (+ r (* step x)) (+ i (* step y)))))))

    (lambda (n)
      (let ((matrix (make-vector (list n n))))
	(mb matrix -1.0 -0.5 0.005 n)
	(if (not (= (matrix 0 0) 5))
	    (format () ";mbrot: ~A~%" n))))))

(mbrot 140)

;;; --------------------------------------------------------------------------------

;;; binary-trees GC benchmark, like gcbench in the standard benchmarks
;;; 18 secs for N=21 (or 22? -- tested originally with +1 below)
;;;   GC time is claiming 6 secs (via (*s7* 'gc-stats)), but callgrind says it's more like 2 secs?
;;;   4Ghz i7-6700, on Mac M4 16 secs, guile 3.0.8 20 secs

(define-constant (bin-make d)
  (if (= d 0)
      (cons #f #f)
      (cons (bin-make (- d 1)) (bin-make (- d 1)))))

(define-constant (bin-check t)
  (if (car t)
      (+ 1 (bin-check (car t)) (bin-check (cdr t)))
      1))

(define (bin-main n)
  (define iterations 0)
  (define min-depth 4)
  (define max-depth (max (+ min-depth 2) n))
  (define stretch-depth (+ max-depth 1))
  (format #t "stretch tree of depth ~a\t check: ~a\n" stretch-depth (bin-check (bin-make stretch-depth)))
  (define long-lived-tree (bin-make max-depth))
  (do ((d min-depth (+ d 2)))
      ((>= d (+ 1 max-depth)))
    (set! iterations (ash 1 (+ (- max-depth d) min-depth)))
    (format #t "~a\t trees of depth ~a\t check: ~a\n"
            iterations
            d
            (do ((sum 0)
		 (i 0 (+ i 1)))
		((= i iterations) sum)
              (set! sum (+ sum (bin-check (bin-make d))))))) ; why is this not fx_treeable?
  (format #t "long lived tree of depth ~a\t check: ~a\n" max-depth (bin-check long-lived-tree)))

(bin-main 14)

;;; stretch tree of depth 15	 check: 65535
;;; 16384 trees of depth 4	 check: 507904
;;; 4096 trees of depth 6	 check: 520192
;;; 1024 trees of depth 8	 check: 523264
;;; 256	 trees of depth 10	 check: 524032
;;; 64	 trees of depth 12	 check: 524224
;;; 16	 trees of depth 14	 check: 524272
;;; long lived tree of depth 14	 check: 32767
;;; Maximum stopping distance 323, starting number 35655
;;;
;;; for 21
;;; stretch      tree of  depth 22	 check: 8388607
;;; 2097152	 trees of depth 4	 check: 65011712
;;; 524288	 trees of depth 6	 check: 66584576
;;; 131072	 trees of depth 8	 check: 66977792
;;; 32768	 trees of depth 10	 check: 67076096
;;; 8192	 trees of depth 12	 check: 67100672
;;; 2048	 trees of depth 14	 check: 67106816
;;; 512	         trees of depth 16	 check: 67108352
;;; 128	         trees of depth 18	 check: 67108736
;;; 32	         trees of depth 20	 check: 67108832
;;; long lived   tree of  depth 21	 check: 4194303

;;; --------------------------------------------------------------------------------

(define collatz
  (let ()
    (define (collatz-count-until-1 n)
      (do ((count 0 (+ count 1)))
	  ((= n 1)
	   count)
	(if (logbit? n 0)
	    (set! n (+ (* 3 n) 1))
	    (set! n (ash n -1)))))
    (lambda (N)
      (let ((len 0)
	    (num 0)
	    (cur 0))
	(do ((i 1 (+ i 1)))
	    ((= i N)) ; 300000))
	  (set! cur (collatz-count-until-1 i))
	  (when (< len cur)
	    (set! len cur)
	    (set! num i)))
	(format *stderr* "Maximum stopping distance ~D, starting number ~D\n" len num)))))

;; (collatz 300000)
;; Maximum stopping distance 442, starting number 230631
;; .45 secs

(collatz 40000)

;;; --------------------------------------------------------------------------------

(define prime?                         ; from exobrain.se
  (let ((increments (list 4 2 4 2 4 6 2 6)))
    (set-cdr! (list-tail increments 7) increments)
    (lambda (z)
      (or (memq z '(2 3 5 7))          ; memv...
	  (and (odd? z)
	       (positive? (remainder z 3))	  
	       (positive? (remainder z 5))	  
	       (positive? (remainder z 7))
	       (do ((L increments)
		    (lim (sqrt z))
		    (divisor 11 (+ divisor (car L))))
		   ((or (zero? (remainder z divisor))
			(>= divisor lim))
		    (> divisor lim))
		 (set! L (cdr L))))))))

(let ()
  (define (count-primes limit)          ; for limit=10M 9.2 secs 664579, 1M .36 secs 78497 (+1 for 1?)
    (let ((primes 1))
      (when (even? limit) 
	(set! limit (+ limit 1)))
      (do ((i 3 (+ i 2)))
	  ((= i limit)
	   primes)
	(if (prime? i)
	    (set! primes (+ primes 1))))))
  (display (count-primes 300000)) (newline)) ; 100000: 9592, 200000: 17984, 300000: 25997

;;; --------------------------------------------------------------------------------
;;;
;;; spectral-norm, based on code by Anthony Borla (Computer Benchmarks Game)

(let ((weights #f))

  (define (mulAv n v av)
    (fill! av 0.0)
    (do ((i 0 (+ i 1)))
	((= i n))
      (do ((j 0 (+ j 1)))
          ((= j n))
	(float-vector-set! av i (+ (float-vector-ref av i) 
				   (/ (float-vector-ref v j)
				      (+ i (float-vector-ref weights (+ i j)))))))))

  (define (mulAtV n v atv)
    (fill! atv 0.0)
    (do ((i 0 (+ i 1)))
	((= i n))
      (do ((j 0 (+ j 1)))
          ((= j n))
	(float-vector-set! atv i (+ (float-vector-ref atv i) 
				    (/ (float-vector-ref v j)
				       (+ j (float-vector-ref weights (+ i j)))))))))

  (define (mulAtAv n v atav)
    (let ((u (make-float-vector n 0.0)))
      (mulAv n v u)
      (mulAtV n u atav)))
  
  (define (spectral-norm n)
    (let ((u (make-float-vector n 1.0))
          (v (make-float-vector n 0.0))
          (vBv 0.0) (vV 0.0))
      
      (set! weights (make-float-vector (* 2 n)))
      (do ((i 0 (+ i 1)))
	  ((= i (* 2 n)))
	(float-vector-set! weights i (+ (* 0.5 i (+ i 1)) 1.0)))
      
      (do ((i 0 (+ i 1)))
          ((= i 10))
	(mulAtAv n u v)
	(mulAtAv n v u))
      
      (do ((i 0 (+ i 1)))
          ((= i n))
	(set! vBv (+ vBv (* (float-vector-ref u i) (float-vector-ref v i))))
	(set! vV (+ vV (* (float-vector-ref v i) (float-vector-ref v i)))))
      
      (sqrt (/ vBv vV))))

  (display (spectral-norm 300)) ; (spectral-norm 4000) takes about 6.8 seconds, 5500: 12.8, 8000: 29.5
  (newline))

;;; --------------------------------------------------------------------------------
;;;
;;; from Joe Marshall (not a "standard benchmark", but interesting)

(let ()
  (define (palindrome? str)
    (or (< (string-length str) 2)
	(and (char=? (string-ref str 0)
		     (string-ref str (- (string-length str) 1)))
	     (palindrome? (substring str 1 (- (string-length str) 1))))))

  (define (pal-test)
    (do ((i 0 (+ i 1)))
	((= i 50000))
      (palindrome? "abcdefgfedcba")
      (palindrome? "abcdefxfedcba")
      (palindrome? "")
      (palindrome? "abcdefghedcba")))

  (pal-test))

;;; this is 5-6 times faster:
;;; (define (palindrome? str) (string=? str (reverse str)))

;;; --------------------------------------------------------------------------------

;;; nbody (from nbody.gcc)

(define-constant solar_mass (* 4 pi pi))
(define-constant days_per_year 365.24)

(define planet-x    (dilambda (lambda (p) (float-vector-ref p 0)) (lambda (p val) (float-vector-set! p 0 val))))
(define planet-y    (dilambda (lambda (p) (float-vector-ref p 1)) (lambda (p val) (float-vector-set! p 1 val))))
(define planet-z    (dilambda (lambda (p) (float-vector-ref p 2)) (lambda (p val) (float-vector-set! p 2 val))))
(define planet-vx   (dilambda (lambda (p) (float-vector-ref p 3)) (lambda (p val) (float-vector-set! p 3 val))))
(define planet-vy   (dilambda (lambda (p) (float-vector-ref p 4)) (lambda (p val) (float-vector-set! p 4 val))))
(define planet-vz   (dilambda (lambda (p) (float-vector-ref p 5)) (lambda (p val) (float-vector-set! p 5 val))))
(define planet-mass (dilambda (lambda (p) (float-vector-ref p 6)) (lambda (p val) (float-vector-set! p 6 val))))

(define (advance nbodies bodies dt)
  (do ((i 0 (+ i 1)))
      ((= i nbodies))
    (let ((b (vector-ref bodies i)))
      (do ((j (+ i 1) (+ j 1)))
	  ((= j nbodies))
	(let* ((b2 (vector-ref bodies j))
	       (dx (- (planet-x b) (planet-x b2)))
	       (dy (- (planet-y b) (planet-y b2)))
	       (dz (- (planet-z b) (planet-z b2)))
	       (distance (sqrt (+ (* dx dx) (* dy dy) (* dz dz))))
	       (mag (/ dt (* distance distance distance))))
	  (set! (planet-vx b) (- (planet-vx b) (* dx (planet-mass b2) mag)))
	  (set! (planet-vy b) (- (planet-vy b) (* dy (planet-mass b2) mag)))
	  (set! (planet-vz b) (- (planet-vz b) (* dz (planet-mass b2) mag)))
	  (set! (planet-vx b2) (+ (planet-vx b2) (* dx (planet-mass b) mag)))
	  (set! (planet-vy b2) (+ (planet-vy b2) (* dy (planet-mass b) mag)))
	  (set! (planet-vz b2) (+ (planet-vz b2) (* dz (planet-mass b) mag)))))))
  (do ((i 0 (+ i 1)))
      ((= i nbodies))
    (let ((b (vector-ref bodies i)))
      (set! (planet-x b) (+ (planet-x b) (* dt (planet-vx b))))
      (set! (planet-y b) (+ (planet-y b) (* dt (planet-vy b))))
      (set! (planet-z b) (+ (planet-z b) (* dt (planet-vz b)))))))

(define (energy nbodies bodies)
  (let ((e 0.0))
    (do ((i 0 (+ i 1)))
	((= i nbodies))
      (let ((b (vector-ref bodies i)))
	(set! e (+ e (* 0.5 (planet-mass b)
			(+ (* (planet-vx b) (planet-vx b))
			   (* (planet-vy b) (planet-vy b))
			   (* (planet-vz b) (planet-vz b))))))
	(do ((j (+ i 1) (+ j 1)))
	    ((= j nbodies))
	  (let* ((b2 (vector-ref bodies j))
		 (dx (- (planet-x b) (planet-x b2)))
		 (dy (- (planet-y b) (planet-y b2)))
		 (dz (- (planet-z b) (planet-z b2)))
		 (distance (sqrt (+ (* dx dx) (* dy dy) (* dz dz)))))
	    (set! e (- e (/ (* (planet-mass b) (planet-mass b2)) distance)))))))
    e))

(define (offset_momentum nbodies bodies)
  (let ((px 0.0)
	(py 0.0)
	(pz 0.0))
    (do ((i 0 (+ i 1)))
	((= i nbodies))
      (let ((b (vector-ref bodies i)))
	(set! px (+ px (* (planet-vx b) (planet-mass b))))
	(set! py (+ py (* (planet-vy b) (planet-mass b))))
	(set! pz (+ pz (* (planet-vz b) (planet-mass b))))))
    (let ((b (vector-ref bodies 0)))
      (set! (planet-vx b) (/ (- px) solar_mass))
      (set! (planet-vy b) (/ (- py) solar_mass))
      (set! (planet-vz b) (/ (- pz) solar_mass)))))

(define-constant NBODIES 5)

(define bodies (vector
		 (float-vector 0  0  0  0  0  0  solar_mass) ; sun

		 (float-vector 4.84143144246472090e+00 
			       -1.16032004402742839e+00 
			       -1.03622044471123109e-01 
			       (* 1.66007664274403694e-03 days_per_year)
			       (* 7.69901118419740425e-03 days_per_year)
			       (* -6.90460016972063023e-05 days_per_year)
			       (* 9.54791938424326609e-04 solar_mass)) ; jupiter

		 (float-vector 8.34336671824457987e+00 
			       4.12479856412430479e+00 
			       -4.03523417114321381e-01 
			       (* -2.76742510726862411e-03 days_per_year)
			       (* 4.99852801234917238e-03 days_per_year)
			       (* 2.30417297573763929e-05 days_per_year)
			       (* 2.85885980666130812e-04 solar_mass)) ; saturn
		 
		 (float-vector 1.28943695621391310e+01 
			       -1.51111514016986312e+01 
			       -2.23307578892655734e-01 
			       (* 2.96460137564761618e-03 days_per_year)
			       (* 2.37847173959480950e-03 days_per_year)
			       (* -2.96589568540237556e-05 days_per_year)
			       (* 4.36624404335156298e-05 solar_mass)) ; uranus

		 (float-vector 1.53796971148509165e+01 
			       -2.59193146099879641e+01 
			       1.79258772950371181e-01 
			       (* 2.68067772490389322e-03 days_per_year)
			       (* 1.62824170038242295e-03 days_per_year)
			       (* -9.51592254519715870e-05 days_per_year)
			       (* 5.15138902046611451e-05 solar_mass)))) ; neptune

(define (main n)
  (offset_momentum NBODIES bodies)
  (format () "~,9F~%" (energy NBODIES bodies))
  (do ((i 0 (+ i 1)))
      ((= i n))
    (advance NBODIES bodies 0.01))
  (format () "~,9F~%" (energy NBODIES bodies)))

(main 6000)

;;; -0.169075164 -0.169064514


;;; --------------------------------------------------------------------------------
(when (> (*s7* 'profile) 0)
  (show-profile 100))
;(when (provided? 'debugging) (display ((*s7* 'memory-usage) :wrappers)) (newline))
(exit)
