;;;; A file of general machine learning utility functions. Always load first.

;;; Variables defining a data set.  See any -DATA file for an example
(defvar *domains*       nil "An ordered list specifying the domain of each feature")
(defvar *feature-names* nil "An ordered list of the names for each feature")
(defvar *categories*    nil "List of categories in a data set")
(defvar *raw-examples*  nil "List of examples in a data file")

;;;;=================================================================================================
;;;; General Utilities
;;;;=================================================================================================


(defmacro trace-print (test-var &rest format-form)
  ;; Print using the format string only if test-var (usually a trace-* variable)is nonNIl
  `(if ,test-var
     (format t ,@format-form)))

(defun seconds-since (time)
   ;;; Return seconds elapsed since given time (initially set by get-internal-run-time)
  (/ (- (get-internal-run-time) time)
     internal-time-units-per-second))

(defun mix-up (list)
  "Randomize the order of elements in this list."
  (mapcar #'(lambda (pair) (rest pair))
	  (sort (mapcar #'(lambda (item) (cons (random 1.0) item)) list)
		#'(lambda (a b) (> (first a) (first b))))))

(defun pick-one (list)
  "Pick an item randomly from the list"
  (nth (random (length list)) list))

(defun append-symbols (&rest symbols)
  "Appends to symbol names and return resulting symbol"
  (intern (format nil "~{~A~}" symbols)))

;;;;=================================================================================================
;;;; Experimental Utilities
;;;;=================================================================================================


(defun train-and-test (system num-train &optional num-test (examples *raw-examples*)
		       (mix-up? t) print-detailed-test-results)
  "Perform standard train and test on the examples by using the first num-train examples
   to train and the remaining to test. Randomize order of examples if mix-up? set. Assumes
   functions of the form system-TRAIN and system-TEST"

 (if mix-up? (setf examples (mix-up examples)))
 (let* ((train-function (append-symbols 'train- system))
	(training-examples (subseq examples 0 num-train))
	(testing-examples  (if num-test
			       (subseq examples (- (length examples) num-test))
			       (subseq examples num-train)))
	(start-time (get-internal-run-time))
	(training-result (funcall train-function training-examples)))
   (format t "~%~%Train time: ~,2Fs" (seconds-since start-time))
   (test-system system training-result testing-examples print-detailed-test-results)))


(defun test-system (system training-result test-examples &optional print-detailed-results)
  "Test the system on the given data and print % correct.  If PRINT-DETAILED-RESULTS
   set then print info about each example"

  (let ((test-function (append-symbols 'test- system))
	(num-examples (length test-examples))
	(num-correct 0)
	answer)
    (dolist (example test-examples)
      (setf answer (funcall test-function example training-result))
      (when (eq answer (first example)) (incf num-correct))
      (trace-print print-detailed-results "~%Real category: ~A; Classified as: ~A   ~A"
		   (first example) answer (if (eq answer (first example)) "" "**WRONG**")))
    (format t "~%~%~A classified ~,2F% of the ~D test cases correctly."
	    test-function (* 100 (/ num-correct num-examples)) num-examples)))
