;;;; -*- Scheme -*-
;;
;; Utilities to support Gunk programs
;;
;; David DeRoure, Sep 11 1997
;; dder@martigny.ai.mit.edu
;;
;; This is an extension of club5d.scm (see HLSIM documentation).
;; It consists of procedures to support Gunk code executing
;; during the simulation, together with procedures to help
;; set up the simulation.  This file should be compiled.
;; NB These two classes of utilities will probably be separated
;; in the future, as Gunk libraries are developed.

;;;;;;;;;;;;;;;;;;
;;
;; club5d.scm
;;

;; Simulation creation and display for club5

(declare (usual-integrations))

(define (make-sim/1 n r)
  (let ((x (make-initialized-vector n (lambda (i) i (random 1.0))))
	(y (make-initialized-vector n (lambda (i) i (random 1.0))))
	(z (make-vector n 0.0)))
    (let ((neighbours (point-neighbours x y z r)))
      (let ((sim (%make-simulation)))
	(do ((i 0 (+ i 1))) ((= i n))
	  (simulation.add-processor sim))
	(set-simulation.x! sim x)
	(set-simulation.y! sim y)
	(set-simulation.z! sim z)
	(set-simulation.neighbours! sim neighbours)
	sim))))


(define (simulation.display! sim flag)
  (define (make-display)
    (let ((g  (make-graphics-device)))
      (graphics-operation g 'set-foreground-color "white")
      (graphics-operation g 'set-background-color "black")
      (graphics-set-coordinate-limits g -.01 -.01 1.01 1.01)
      (graphics-clear g)
      g))
  (set-simulation.display!
   sim
   (cond ((not flag)  #F)
	 ((graphics-device? flag) flag)
	 (else (make-display)))))
      
(define (%color-me sim i color)
  (define d 0.007)
  (define colors '#("blue" "orange" "yellow" "green" "cyan"
		    "magenta" "pink"))

  (let ((g (simulation.display sim)))

    (define (draw color)
      (let ((x (vector-ref (simulation.x sim) i))
	    (y (vector-ref (simulation.y sim) i)))
	(graphics-operation g 'set-foreground-color color)
	(graphics-draw-text g x y ".")))

    (cond ((not g) unspecific)
	  ((string? color)
	   (draw color))
	  ((exact-integer? color)
	   (if (and (<= 0 color)
		    (< color (vector-length colors)))
	       (draw (vector-ref colors color))
	       (draw "white")))
	  (else
	   (error:wrong-type-argument color "color string"
				      'color-me)))))


;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; These extra routines assist simulation setup

;; simulation.find-processor-nearest is a useful library routine
;; which searches an existing simulation for the processor closest
;; to the given coordinates.

(define (simulation.find-processor-nearest sim x y #!optional z)
  (define (sq x) (* x x))
  (let ((z (if (default-object? z) 0 z))
	(x-vector (simulation.x sim))
	(y-vector (simulation.y sim))
	(z-vector (simulation.z sim))
	(r2-min 1e99)   ; the smallest square of distance found (start > 3)
	(min-i #F))     ; index of the processor at r2-min (else #F)
    (do ((i 0 (+ i 1))) ((= i (simulation.n-processors sim)))
      (let ((r2 (+ (sq (- x (vector-ref x-vector i)))
		   (sq (- y (vector-ref y-vector i)))
		   (sq (- z (vector-ref z-vector i))))))
	(if (< r2 r2-min)
	    (begin
	      (set! min-i i)
	      (set! r2-min r2)))))
    min-i))

;; simulation.find-processors-in-region is a useful library routine which 
;; searches an existing simulation for all the processors which satisfy a 
;; predicate (specified as a function of the processor coordinates).
;; NB An alternative approach to specifying arbitrary regions is to use
;; a drawing package to produce a PNM image for the HLSIM sensor facility.

(define (simulation.find-processors-in-region sim in-region?)
  (let ((x-vector (simulation.x sim))
	(y-vector (simulation.y sim))
	(z-vector (simulation.z sim))
	(n (simulation.n-processors sim)))
    (let loop ((i 0) (result '()))
	 (cond ((= i n) result)
	       ((in-region? (vector-ref x-vector i)
			    (vector-ref y-vector i)
			    (vector-ref z-vector i)) 
		(loop (1+ i) (cons i result)))
	       (else (loop (1+ i) result))))))

;; The make-circle-predicate procedure returns a predicate for use in
;; simulation.find-processors-in-region.  The predicate describes a
;; circular region with origin at (x, y) and radius r (excluding points
;; with radius equal to r).  For example, to find processors in a circle
;; centered at (0.5, 0.5) with radius 0.1, use
;; (simulation.find-processors-in-region sim (make-circle-predicate .5 .5 .1))

(define (make-circle-predicate x0 y0 r)
  (let ((sq (lambda (a) (* a a)))
	(r2 (* r r)))
       (lambda (x y z)
	       (< (+ (sq (- x x0)) (sq (- y y0))) r2))))
	
;; Alternative version of make-sim/1, with variable neighborhood density.
;; The f argument should be a function which maps reals generated by
;; (random 1.0) to values in the same range, e.g. using expt.  If no f 
;; argument is supplied, it defaults to sqrt.  This function can be
;; generalised to take the vector initialization functions as arguments,
;; in wich case the coordinates of each processor could be a function of 
;; the processor index.

(define (make-sim/f n r #!optional f)
  (let ((f (if (default-object? f) sqrt f)))
    (let ((x (make-initialized-vector n (lambda (i) i (f (random 1.0)))))
	  (y (make-initialized-vector n (lambda (i) i (f (random 1.0)))))
	  (z (make-vector n 0.0)))
      (let ((neighbours (point-neighbours x y z r)))
	(let ((sim (%make-simulation)))
	  (do ((i 0 (+ i 1))) ((= i n))
	    (simulation.add-processor sim))
	  (set-simulation.x! sim x)
	  (set-simulation.y! sim y)
	  (set-simulation.z! sim z)
	  (set-simulation.neighbours! sim neighbours)
	  sim)))))

;; Make jiggly simulation.  The j argument is the jigglyness, 0 <= j < 1
;; If no j argument is supplied, it defaults to 0.5 (which gives a jiggly
;; but visibly uniform distribution).  j=0 => no random component.
;; NB If n is not a square number, n processors will still be created,
;; but there will be gaps in the grid near the top right.

(define (make-sim/j n r #!optional j)
  (let ((j (if (default-object? j) 0.5 j))
	(d (ceiling (sqrt n))) ; number of divisions on axes of unit square
	(x (make-vector n))
	(y (make-vector n))
	(z (make-vector n 0.0)))
    (let loop ((p-i 0) (x-i 0) (y-i 0))
      (cond ( (= p-i n) 'done )
	    ( (= x-i d) (loop p-i 0 (1+ y-i)) )
	    ( else (vector-set! x p-i (/ (+ x-i (* j (random 1.0))) d))
		   (vector-set! y p-i (/ (+ y-i (* j (random 1.0))) d))
		   (loop (1+ p-i) (1+ x-i) y-i) )))
    ;; remaining code is identical to make-sim/1
    (let ((neighbours (point-neighbours x y z r)))
      (let ((sim (%make-simulation)))
	(do ((i 0 (+ i 1))) ((= i n))
	  (simulation.add-processor sim))
	(set-simulation.x! sim x)
	(set-simulation.y! sim y)
	(set-simulation.z! sim z)
	(set-simulation.neighbours! sim neighbours)
	sim))))

;; Make constrained simulation, where all processors satisfy the
;; constraint predicate c?, a function of the number of neighbors.
;; If no constraint is provided, it constructs a simulation where
;; all processors have more than two neighbors (a "clumpy" simulation).
;; NB This procedure simply iterates until the constraint is satisfied, 
;; with no limit to the number of iterations.  

(define (make-sim/c n r #!optional c?) 
  (let ((c? (if (default-object? c?) (lambda (n) (> n 1)) c?))
	(x (make-initialized-vector n (lambda (i) i (random 1.0))))
	(y (make-initialized-vector n (lambda (i) i (random 1.0))))
	(z (make-vector n 0.0)))

    (let loop ((neighbours (point-neighbours x y z r)) (tries 1))

      (define (count-rejects i rejects)
	(cond ((= i n) rejects)
	      ((c? (vector-length (vector-ref neighbours i)))
		   (count-rejects (1+ i) rejects))
	      (else (vector-set! x i (random 1.0))
		    (vector-set! y i (random 1.0))
		    (count-rejects (1+ i) (1+ rejects)))))
      
	(if (zero? (count-rejects 0 0))
	    (let ((sim (%make-simulation)))
	      (do ((i 0 (+ i 1))) ((= i n))
		(simulation.add-processor sim))
	      (set-simulation.x! sim x)
	      (set-simulation.y! sim y)
	      (set-simulation.z! sim z)
	      (set-simulation.neighbours! sim neighbours)
	      sim)
	    (loop (point-neighbours x y z r) (1+ tries))))))

;; end of util1d.scm
