about summary refs log tree commit diff
path: root/paip
diff options
context:
space:
mode:
authorNguyễn Gia Phong <vn.mcsinyx@gmail.com>2019-07-04 22:11:37 +0700
committerNguyễn Gia Phong <vn.mcsinyx@gmail.com>2019-07-04 22:11:37 +0700
commit6c0ea3cc3364733e8d8fe7625b128f817d55b2cd (patch)
tree1904deddc2c416b4acc88a59c91080b139e35341 /paip
parent0c84539e6040bdb918a1d0e386f033da17170348 (diff)
downloadcp-6c0ea3cc3364733e8d8fe7625b128f817d55b2cd.tar.gz
[PAIP] Upload GPS and move on
Diffstat (limited to 'paip')
-rw-r--r--paip/gps.lisp559
1 files changed, 559 insertions, 0 deletions
diff --git a/paip/gps.lisp b/paip/gps.lisp
new file mode 100644
index 0000000..8501bc1
--- /dev/null
+++ b/paip/gps.lisp
@@ -0,0 +1,559 @@
+(defvar *dbg-ids* nil "Identifiers used by dbg.")
+
+(defun debug-pseudo (&rest ids)
+  "Start dbg output on the given ids."
+  (setf *dbg-ids* (union ids *dbg-ids*)))
+
+(defun undebug-pseudo (&rest ids)
+  "Stop dbg on the ids. With no ids, stop dbg altogether."
+  (setf *dbg-ids* (unless (null ids) (set-difference *dbg-ids* ids))))
+
+(defun dbg (id indent format-string &rest args)
+  "Print debugging info if (DEBUG ID) has been specified."
+  (when (member id *dbg-ids*)
+    (format *debug-io* "~&~V@T~?" (* 2 indent) format-string args)))
+
+(defvar *ops* nil "A list of available operators.")
+
+(defstruct op
+  "An operation"
+  (action nil)
+  (preconds nil)
+  (add-list nil)
+  (del-list nil))
+
+(defun starts-with? (list x)
+  "Is this a list whose first element is x?"
+  (and (consp list) (eql (first list) x)))
+
+(defun executing? (x)
+  "Is x of the form: (executing ...) ?"
+  (starts-with? x 'executing))
+(defun action? (state)
+  "Is state an action?"
+  (or (equal state '(start)) (executing? x)))
+
+(defun convert-op! (op)
+  "Make op conform to the (EXECUTING op) convention."
+  (unless (some #'executing? (op-add-list op))
+    (push (list 'executing (op-action op)) (op-add-list op)))
+  op)
+
+(defun op (action &key preconds add-list del-list)
+  (convert-op! (make-op :action action
+                        :preconds preconds
+                        :add-list add-list
+                        :del-list del-list)))
+
+(defun appropriate? (goal op)
+  "An op is appropriate to a goal if it's in its add-list."
+  (member goal (op-add-list op) :test #'equal))
+
+(defun apply-op (state goal op goal-stack)
+  "Return a new, transformed state if op is applicable."
+  (dbg 'gps (length goal-stack) "; Consider: ~a" (op-action op))
+  (let ((state2 (achieve-all state (op-preconds op) (cons goal goal-stack))))
+    (unless (null state2)
+      (dbg 'gps (length goal-stack) "; Action: ~a" (op-action op))
+      (append (remove-if (lambda (x) (member x (op-del-list op) :test #'equal))
+                         state2)
+              (op-add-list op)))))
+
+(defun appropriate-ops (goal state)
+  "Return a list of appropriate operators,
+  sorted by the number of unfulfulled preconditions."
+  (sort (copy-list (remove-if-not (lambda (op) (appropriate? goal op)) *ops*))
+        #'< :key (lambda (op)
+                   (count-if (lambda (precond)
+                               (not (member precond state :test #'equal)))
+                             (op-preconds op)))))
+
+(defun achieve (state goal goal-stack)
+  "A goal is achieved if it already holds,
+  or if there is an appropriate op for it that is applicable."
+  (dbg 'gps (length goal-stack) "; Goal: ~a" goal)
+  (cond ((member goal state :test #'equal) state)
+        ((member goal goal-stack :test #'equal) nil) ; in case of infinite loop
+        (t (some (lambda (op) (apply-op state goal op goal-stack))
+                 (appropriate-ops goal state)))))
+
+(defun achieve-all (state goals goal-stack)
+  "Try to achieve each goal, then make sure they still hold."
+  (let ((current-state state))
+    (if (and (every (lambda (g) (setf current-state
+                                      (achieve current-state g goal-stack)))
+                    goals)
+             (subsetp goals current-state :test #'equal))
+        current-state)))
+
+(defun GPS (state goals &optional (*ops* *ops*))
+  "General Problem Solver: achieve all goals from current *state* using *ops*."
+  (remove-if-not #'action? (achieve-all (cons '(start) state) goals nil)))
+
+(defun use! (oplist)
+  "Use oplist as the default list of operators."
+  (length (setf *ops* oplist)))
+
+; Drive son to school
+(defparameter *school-ops*
+  (list
+   (make-op :action 'drive-son-to-school
+            :preconds '(son-at-home car-works)
+            :add-list '(son-at-school)
+            :del-list '(son-at-home))
+   (make-op :action 'shop-installs-battery
+            :preconds '(car-needs-battery shop-knows-problem shop-has-money)
+            :add-list '(car-works))
+   (make-op :action 'tell-shop-problem
+            :preconds '(in-comunication-with-shop)
+            :add-list '(shop-knows-problem))
+   (make-op :action 'telephone-shop
+            :preconds '(know-phone-number)
+            :add-list '(in-comunication-with-shop))
+   (make-op :action 'ask-phone-number
+            :preconds '(in-comunication-with-shop)
+            :add-list '(know-phone-number))
+   (make-op :action 'look-up-number
+            :preconds '(have-phone-book)
+            :add-list '(know-phone-number))
+   (make-op :action 'give-shop-money
+            :preconds '(have-money)
+            :add-list '(shop-has-money)
+            :del-list '(have-money))))
+(mapc #'convert-op! *school-ops*)
+
+; Monkey and Bananas
+(defparameter *banana-ops*
+  (list (op 'climb-on-chair
+            :preconds '(chair-at-middle-room at-middle-room on-floor)
+            :add-list '(at-bananas on-chair)
+            :del-list '(at-middle-room on-chair))
+        (op 'push-chair-from-door-to-middle-room
+            :preconds '(chair-at-door at-door)
+            :add-list '(chair-at-middle-room at-middle-room)
+            :del-list '(chair-at-door at-door))
+        (op 'walk-from-door-to-middle-room
+            :preconds '(at-door on-floor)
+            :add-list '(at-middle-room)
+            :del-list '(at-door))
+        (op 'grasp-bananas
+            :preconds '(at-bananas empty-handed)
+            :add-list '(has-bananas)
+            :del-list '(empty-handed))
+        (op 'drop-ball
+            :preconds '(has-ball)
+            :add-list '(empty-handed)
+            :del-list '(has-ball))
+        (op 'eat-bananas
+            :preconds '(has-bananas)
+            :add-list '(empty-handed not-hungry)
+            :del-list '(has-bananas hungry))))
+
+; Maze searching
+(defun make-maze-op (src dest)
+  "Make an operator to more between two places."
+  (op `(move from ,src to ,dest)
+      :preconds `((at ,src))
+      :add-list `((at ,dest))
+      :del-list `((at ,src))))
+
+(defun make-maze-ops (pairs)
+  "Make maze ops from list of directions."
+  (unless (null pairs)
+    (let ((pair (first pairs)))
+      (list* (make-maze-op (car pair) (cdr pair))
+             (make-maze-op (cdr pair) (car pair))
+             (make-maze-ops (rest pairs))))))
+
+(defparameter *maze-ops*
+  (make-maze-ops
+   '((1 . 2) (2 . 3) (3 . 4) (4 . 9) (9 . 14) (9 . 8) (8 . 7)
+     (7 . 12) (12 . 13) (12 . 11) (11 . 6) (11 . 16) (16 . 17)
+     (17 . 22) (21 . 22) (22 . 23) (23 . 18) (23 . 24) (24 . 19)
+     (19 . 20) (20 . 15) (15 . 10) (10 . 5) (20 . 25))))
+
+(defun find-path (start end)
+  "Search the maze for a path from start to end."
+  (let ((result (GPS `((at ,start)) `((at ,end)) *maze-ops*)))
+    (unless (null result)
+      (cons start (mapcar (lambda (action) (fifth (second action)))
+                          (rest result))))))
+
+; Blocks World
+(defun move-op (a b c)
+  "Make an operator to move a from b to c."
+  (labels ((move-ons (a b c)
+             (if (eq b 'table)
+                 `((,a on ,c))
+                 `((,a on ,c) (space on ,b)))))
+    (op `(move ,a from ,b to ,c)
+        :preconds `((space on ,a) (space on ,c) (,a on ,b))
+        :add-list (move-ons a b c)
+        :del-list (move-ons a c b))))
+
+(defun make-block-ops (blocks)
+  "Create an operator for each possible block move."
+  (let ((ops nil))
+    (dolist (a blocks)
+      (dolist (b blocks)
+        (unless (equal b a)
+          (push (move-op a 'table b) ops)
+          (push (move-op a b 'table) ops)
+          (dolist (c blocks)
+            (unless (or (equal c a) (equal b c))
+              (push (move-op a b c) ops))))))
+    ops))
+
+(defun range (start stop &optional (step 1))
+  "Return a list of numbers from start (inclusive) to stop (exclusive) by step."
+  (let (result)
+    (do ((a start (+ a step)))
+        ((>= (* (- a stop) step) 0) (reverse result))
+      (push a result))))
+(defun permutations (list &optional r)
+  "Return successive r-length permutations of elements in the list."
+  (let* ((n (length list))
+         (r (or r n)))
+    (unless (> r n)
+      (let ((indices (range 0 n))
+            (cycles (range n (- n r) -1))
+            (rrr (range (1- r) -1 -1))
+            (result (list (subseq list 0 r))))
+        (do ((going nil (dolist (i rrr t)
+                          (mapc #'print `(,i ,indices ,cycles ,result))
+                          (decf (elt cycles i))
+                          (let ((j (elt cycles i)))
+                            (cond ((= j 0)
+                                   (setf (elt cycles i) (- n i))
+                                   (let ((tail (elt indices i)))
+                                     (nconc (delete tail indices) (list tail))))
+                                  (t (rotatef (elt indices (- n j))
+                                              (elt indices i))
+                                     (push (mapcar (lambda (k) (elt list k))
+                                                   (subseq indices 0 r))
+                                           result)
+                                     (return)))))))
+            (going (reverse result)))))))
+
+; Searching tools
+(defconstant fail nil "Indication of search failure.")
+(defun tree-search (states goal? successors combiner)
+  "Find a state that satisfies goal?. Start with states,
+  and search according to successors and combiner."
+  (dbg 'search 0 ";; Search: ~a" states)
+  (cond ((null states) fail)
+        ((funcall goal? (first states)) (first states))
+        (t (tree-search (funcall combiner
+                                 (funcall successors (first states))
+                                 (rest states))
+                        goal? successors combiner))))
+(defun tree-do-search (states goal? successors combiner)
+  "Find a state that satisfies goal?. Start with states, and search according to
+  successors and combiner. Use an explicit loop rather than recursion."
+  (if (null states)
+      fail
+      (do ((current-states states
+                           (funcall combiner
+                                    (funcall successors (first current-states))
+                                    (rest current-states))))
+          ((null current-states) fail)
+        (dbg 'search 0 ";; Search: ~a" current-states)
+        (when (funcall goal? (first current-states))
+          (return (first current-states))))))
+
+(defun prepend (new old) (append old new))
+(defun is (value &key (key #'identity) (test #'eql))
+  (lambda (x) (funcall test (funcall key x) value)))
+
+(defun dfs (start goal? successors)
+  "Search new states first until goal is reached."
+  (tree-search (list start) goal? successors #'append))
+(defun bfs (start goal? successors)
+  "Search old states first until goal is reached."
+  (tree-search (list start) goal? successors #'prepend))
+
+(defun binary-tree (x) (list (* x 2) (+ x x 1)))
+(defun finite-binary-tree (n)
+  "Return a successor function that generates a binary tree with n nodes."
+  (lambda (x) (remove-if (lambda (child) (> child n))
+                         (binary-tree x))))
+(defun next2 (x) (list (+ x 1) (+ x 2)))
+
+(defun sorter (cost-fn)
+  "Return a combiner function that sorts according to cost-fn."
+  (lambda (new old)
+    (merge 'list (sort new #'< :key cost-fn) old #'< :key cost-fn)))
+(defun best-1st-search (start goal? successors cost-fn)
+  "Search the lowest cost states first until goal is reached."
+  (tree-search (list start) goal? successors (sorter cost-fn)))
+
+(defun diff (num)
+  "Return the function that finds the difference from num."
+  (lambda (x) (abs (- x num))))
+(defun price-is-right (price)
+  "Return a function that measures the difference from price,
+  but give a penalty for going over the price."
+  (lambda (x) (if (> x price)
+                  most-positive-fixnum
+                  (- price x))))
+
+(defun beam-search (start goal? successors cost-fn beam-width)
+  "Search highest scoring states first until goal is reached,
+  but never consider more than beam-width states at a time."
+  (tree-search (list start) goal? successors
+               (lambda (new old)
+                 (let ((sorted (funcall (sorter cost-fn) new old)))
+                   (if (<= (length sorted) beam-width)
+                       sorted
+                       (subseq sorted 0 beam-width))))))
+(defun iter-wide-search (start goal? successors cost-fn
+                         &key (width 1) (max 100))
+  "Search, increasing beam width from width to max.
+  Return the first solution found at any width."
+  (dbg 'search 0 "; Width: ~d" width)
+  (unless (> width max)
+    (or (beam-search start goal? successors cost-fn width)
+        (iter-wide-search start goal? successors cost-fn
+                          :width (1+ width) :max max))))
+(defun search-all (start goal? successors cost-fn beam-width)
+  "Find all solutions to a search problem, using beam search."
+  (let ((solutions nil))
+    (beam-search start
+                 (lambda (x) (when (funcall goal? x) (push x solutions)) nil)
+                 successors cost-fn beam-width)
+    solutions))
+
+(defstruct (city (:type list)) name long lat)
+(defparameter *cities*
+  '((Atlanta 84.23 33.45)
+    (Boston 71.05 42.21)
+    (Chicago 87.37 41.50)
+    (Denver 105.00 39.45)
+    (Eugene 123.05 44.03)
+    (Flagstaff 111.41 35.13)
+    (Grand-Jet 108.37 39.05)
+    (Houston 105.00 34.00)
+    (Indianapolis 86.10 39.46)
+    (Jacksonville 81.40 30.22)
+    (Kansas-City 94.35 39.06)
+    (Los-Angeles 118.15 34.03)
+    (Memphis 90.03 35.09)
+    (New-York 73.58 40.47)
+    (Oklahoma-City 97.28 35.26)
+    (Pittsburgh 79.57 40.27)
+    (Quebec 71.11 46.49)
+    (Reno 119.49 39.30)
+    (San-Francisco 122.26 37.47)
+    (Tampa 82.27 27.57)
+    (Victoria 123.21 48.25)
+    (Wilmington 77.57 34.14)))
+
+(defconstant earth-diameter 12765.0 "Diameter of planet Earth in kilometers.")
+(defun distance (point1 point2)
+  "The Euclidean distance between two points.
+  The points are coordinates in n-dimensional space."
+  (sqrt (reduce #'+ (mapcar (lambda (a b) (expt (- a b) 2)) point1 point2))))
+(defun deg->rad (deg)
+  "Convert degrees and minutes to radians."
+  (* (+ (truncate deg) (* (rem deg 1) 100/60)) pi 1/180))
+(defun xyz-coords (city)
+  "Return the (x y z) coordinates of a point on a sphere.
+  The center is (0 0 0) and the north pole is (0 0 1)."
+  (let ((psi (deg->rad (city-lat city)))
+        (phi (deg->rad (city-long city))))
+    (list (* (cos psi) (cos phi))
+          (* (cos psi) (sin phi))
+          (sin psi))))
+(defun air-distance (city1 city2)
+  "The great circle distance between two cities."
+  (let ((d (distance (xyz-coords city1) (xyz-coords city2))))
+    (* earth-diameter (asin (/ d 2)))))
+
+(defun neighbor-cities (city)
+  "Find all cities within 1000 kilometers."
+  (remove-if (lambda (c) (or (eq c city) (> (air-distance c city) 1000.0)))
+             *cities*))
+(defun city (name) "Find the city with this name." (assoc name *cities*))
+
+(defstruct (path (:print-function print-path))
+  state (previous nil) (cost-so-far 0) (total-cost 0))
+(defun print-path (path &optional (stream t) depth)
+  (declare (ignore depth))
+  (format stream "#<Path to ~a costs ~,1f>"
+          (path-state path) (path-total-cost path)))
+(defun map-path (fn path)
+  "Call fn on each state in the path, collecting the results."
+  (unless (null path)
+    (cons (funcall fn (path-state path))
+          (map-path fn (path-previous path)))))
+(defun show-city-path (path &optional (stream t))
+  "Show the length of a path, and the cities along it."
+  (format stream "#<Path ~,1f km: ~{~:(~a~)~^ - ~}>"
+          (path-total-cost path) (reverse (map-path #'city-name path)))
+  (values))
+
+(defun path-saver (successors cost-fn cost-left-fn)
+  (lambda (old-path)
+    (let ((old-state (path-state old-path)))
+      (mapcar (lambda (new-state)
+                (let ((old-cost (+ (path-cost-so-far old-path)
+                                   (funcall cost-fn old-state new-state))))
+                  (make-path :state new-state
+                             :previous old-path
+                             :cost-so-far old-cost
+                             :total-cost (+ (funcall cost-left-fn new-state)
+                                            old-cost))))
+              (funcall successors old-state)))))
+(defun trip (start dest &optional (beam-width 1))
+  "Search for a way from the start to dest."
+  (beam-search (make-path :state start)
+               (is dest :key #'path-state)
+               (path-saver #'neighbor-cities #'air-distance
+                           (lambda (c) (air-distance c dest)))
+               #'path-total-cost
+               beam-width))
+
+(defun new-states (states successors state= old-states)
+  "Generate successor states that have not been seen before."
+  (remove-if (lambda (state) (or (member state old-states :test state=)
+                                 (member state states :test state=)))
+             (funcall successors (first states))))
+(defun graph-search (states goal? successors combiner
+                     &optional (state= #'eql) old-states)
+  "Find a state that satisfies goal?. Start with states and search
+  according to successors and combiner. Don't try the same state twice."
+  (dbg 'search 0 ";; Search: ~a" states)
+  (cond ((null states) fail)
+        ((funcall goal? (first states)) (first states))
+        (t (graph-search (funcall combiner
+                                  (new-states states successors state= old-states)
+                                  (rest states))
+                         goal? successors combiner state=
+                         (adjoin (first states) old-states :test state=)))))
+(defun hash-graph-search (states goal? successors combiner
+                          &optional (state= #'eql)
+                            (old-states
+                             (let ((os (make-hash-table :test state=)))
+                               (mapcar (lambda (state)
+                                         (setf (gethash state os) t))
+                                       states)
+                               os)))
+  "Find a state that satisfies goal?. Start with states and search according to
+  successors and combiner. Use hash-table to test and avoid states that
+  has been seen before."
+  (dbg 'search 0 ";; Search: ~a" states)
+  (cond ((null states) fail)
+        ((funcall goal? (first states)) (first states))
+        (t (setf (gethash (first states) old-states) t)
+           (hash-graph-search
+            (funcall combiner
+                     (remove-if-not
+                      (lambda (state) (unless (gethash state old-states)
+                                        (setf (gethash state old-states) t)))
+                      (funcall successors (first states)))
+                     (rest states))
+            goal? successors combiner state= old-states))))
+
+(defun path-states (path)
+  "Collect the states along this path."
+  (map-path #'identity path))
+(defun find-path (state paths state=)
+  "Find the path with this state among a list of paths."
+  (find state paths :key #'path-state :test state=))
+(defun better-path? (path1 path2)
+  "Is path1 cheaper than path2?"
+  (< (path-total-cost path1) (path-total-cost path2)))
+(defun insert-path (path paths)
+  "Put path into the right position, sorted by total cost."
+  (merge 'list (list path) paths #'< :key #'path-total-cost))
+(defun a*-search (paths goal? successors cost-fn cost-left-fn
+                  &optional (state= #'eql) old-paths)
+  "Find a path whose state satisfies goal?. Start with paths,
+  and expand successors, exploring least cost first.
+  When there are duplicate states, keep the one with the lower cost
+  and discard the other."
+  (dbg 'search 0 ";; Search: ~a" paths)
+  (cond ((null paths) fail)
+        ((funcall goal? (path-state (first paths)))
+         (values (first paths) paths))
+        (t (let* ((path (pop paths))
+                  (state (path-state path)))
+             (setf old-paths (insert-path path old-paths))
+             (dolist (state2 (funcall successors state))
+               (let* ((cost (+ (path-cost-so-far path)
+                               (funcall cost-fn state2)))
+                      (cost2 (funcall cost-left-fn state2))
+                      (path2 (make-path :state state2 :previous path
+                                        :cost-so-far cost
+                                        :total-cost (+ cost cost2)))
+                      old)
+                 (cond ((setf old (find-path state2 paths state=))
+                        (when (better-path path2 old)
+                          (setf paths (insert-path path2 (delete old paths)))))
+                       ((setf old (find-path state2 old-paths state=))
+                        (when (better-path path2 old)
+                          (setf paths (insert-path path2 paths))
+                          (setf old-paths (delete old old-paths))))
+                       (t (setf paths (insert-path path2 paths))))))
+             (a*-search paths goal? successors cost-fn cost-left-fn
+                        state= old-paths)))))
+(defun hash-a*-search (paths goal? successors cost-fn cost-left-fn
+                       &optional (state= #'eql) old-paths)
+  "Find a path whose state satisfies goal?. Start with paths, and expand
+  successors, exploring least cost first. When there are duplicate states,
+  which are logged in a hash-table, keep the one with the lower cost
+  and discard the other."
+  (dbg 'search 0 ";; Search: ~a" paths)
+  (cond ((null paths) fail)
+        ((funcall goal? (path-state (first paths)))
+         (values (first paths) paths))
+        (t (let* ((path (pop paths))
+                  (state (path-state path)))
+             (setf old-paths (insert-path path old-paths))
+             (dolist (state2 (funcall successors state))
+               (let* ((cost (+ (path-cost-so-far path)
+                               (funcall cost-fn state2)))
+                      (cost2 (funcall cost-left-fn state2))
+                      (path2 (make-path :state state2 :previous path
+                                        :cost-so-far cost
+                                        :total-cost (+ cost cost2)))
+                      old)
+                 (cond ((setf old (find-path state2 paths state=))
+                        (when (better-path path2 old)
+                          (setf paths (insert-path paths (delete old paths)))))
+                       ((setf old (find-path state2 old-paths state=))
+                        (when (better-path path2 old)
+                          (setf paths (insert-path path2 paths))
+                          (setf old-paths (delete old old-paths))))
+                       (t (setf paths (insert-path path2 paths))))))
+             (hash-a*-search paths goal? successors cost-fn cost-left-fn
+                             state= old-paths)))))
+
+(defun applicable-ops (state)
+  "Return a list of all ops that are applicable now."
+  (remove-if-not (lambda (op) (subsetp (op-preconds op) state :test #'equal))
+                 *ops*))
+(defun gps-successors (state)
+  "Return a list of states reachable from this one using ops."
+  (mapcar (lambda (op)
+            (append (remove-if (lambda (x)
+                                 (member x (op-del-list op) :test #'equal))
+                               state)
+                    (op-add-list op)))
+          (applicable-ops state)))
+(defun search-gps (start goal &optional (beam-width 10))
+  "Search for a sequence of operators leading to goal."
+  (remove-if-not
+   #'action?
+   (beam-search (cons '(start) start)
+                (lambda (state) (subsetp goal state :test #'equal))
+                #'gps-successors
+                (lambda (state)
+                  (+ (cound-if #'action? state)
+                     (count-if (lambda (con)
+                                 (not (member con state :test #'equal))))))
+                beam-width)))
+
+(defun compose (&rest functions)
+  "Return the function that except one argument and nestedly call functions."
+  (lambda (x) (reduce #'funcall functions :from-end t :initial-value x)))