2.古典的プラニングの表現

 Ghallab らによる Automated Planning Theory and Practice(APTP) では,第2章でプラニングについて基礎的な概念や表現を導入します.ここではなるべくAPTPに合わせて,それを Lisp でどう表現するか,説明していきます.節立てはかならずしもAPTPとあっていませんが,章立ては合わせていこうと思っています.

2.1 命題シンボル,集合的操作,状態遷移関数

 命題シンボルの有限集合が,領域の語彙 L を形成します.Lisp を用いて命題シンボルは Lisp のシンボルで,その集合をリストで表現することにします.たとえば,APTP の Example 2.1 では,次の五つの語彙があります.

 そのとき,ある状態 s0 = { onground, at2 } や s5 = { onrobot, at2 } は次のように Lisp で表現できます.

[例1]

> (setq s0 '(onground at2))
> (setq s5 '(onrobot at2))


 ここでsetqは第1引数にシンボルをとり,そのシンボルの値として第2引数の値を代入します.(onground at2)(onrobot at2)の前にある は quote と言って,その値を評価することを抑制します.すなわち,この場合は(onground at2)(onrobot at2)が評価されることを抑制して,それそのものが第2引数の値として用いられます.

 実際にロボットがコンテナを運んだり,クレーンがコンテナを持ったりすると,状態が変わります.たとえば,初期状態 s0 = { onground, at2 } からクレーンがコンテナを取れば,状態から onground が消えて,holding 付け加わりますが,そのような集合的操作は,Lisp の集合に関する関数を使って自然に表現できます.

[例2]

> (setq s1 (union (set-difference s0 '(onground)) '(holding)))
(at2 holding)
> s1
(at2 holding)


 union も set-difference もリストである引数を集合と解釈しますが,前者は合併集合を,後者は差集合を作って返します.

 引数に状態と add-list と del-list をとって,上記のような状態遷移を行う関数は次のように定義できます.

[例3]

> (defun progress (state add-list del-list)
    (union (set-difference state del-list) add-list))
progress
> (setq s1 (progress s0 '(holding) '(onground)))
(at2 holding)

 
 ここで defun は Lisp における関数定義のマクロであり,引数としてstate,add-list,del-list をパラメータと指定しています.その次にある union から始まる行が関数本体であり,関数本体(ここでは1行のみ)の最後の行の値がこの関数全体の値となります.上記の関数 progress の実施例では,例2と同じことを関数 progress を用いて行っています.

2.2 状態と状態変数

 前節の命題シンボルによる状態の表現ではロボットとかクレーンに曖昧さが残るので,たとえばロボット r1 が場所1にいることを,at1-r1 などと書くようにすると今度はロボットや場所の組み合わせによって語彙の数が膨大に増えてしまいます.そこでロボット r1 が場所 loc1 にいることを at(r1,loc1) などと書くようにします.at はこの場合述語論理における述語と考えてもよいですし,引数を2個とって真偽値を返す関数と考えてもいいです.ロボットが移動するとこの値は変わりますから,つまり状態ごとにこの真偽値は変化しますから,このようなものを状態変数と呼びます.

 我々のプログラムでは状態変数は,名前とパラメータとその値を持つ構造体として定義します.

[定義1]

> (defstruct statevar name parms val)
statevar 

 
 ここで defstruct は Lisp の構造体を定義するマクロで,statevarという構造体はその構成要素として name,parms,val を持つものとしています.状態変数 at(r1,loc1) は次のように定義できます.

[例4]

> (setq foo (make-statevar :name 'at :parms '(r1 loc1) :val t))
#S(statevar :name at :parms (r1 loc1) :val t)
> (statevar-name foo)
at
> (statevar-parms foo)
(r1 loc1)
> (statevar-val foo)
t

 
 構造体 statevar を定義するとそのコンストラクタ関数 make-statevar が自動的に定義され,その使用法は上記のように構造体の定義内容がわかっていれば,わかります.val に設定している t は Lisp における Boolean みたいなもので真を意味します.ここでは返された構造体がそのままプリントされていますね.#S( )は構造体を意味する Lisp の表記です.メンバーの値を取り出すアクセス関数も自動的に定義されますので,上記例のようにして値を取り出すことができます.
 statevar というのは状態変数を表す Lisp における型であり,そのインスタンスとして名前やパラメータや値を持つ構造体を作ることができます.ここでは同じ名前や,同じパラメータを持つインスタンスをいくらでも作ることができることに注意してください.

 プログラム的には,その時々の状態をひとつにまとめて扱うことが必要です.そしてその状態はプログラムのすべての場所から一意に参照可能でなければなりません.そのようなしくみに適したものがグローバル変数です.ここでグローバル変数 *state* を定義します.この変数にはその時々のすべての状態変数の集合がリストとして代入されるものとします.

[定義2]

> (defvar *state* ())
*state* 

 
 defvar は Lisp のグローバル変数定義マクロで,このようにしたシンボルは通常プログラム中のどこからでも同じものを参照します.上記定義ではその参照時の初期値として空リスト()を与えています.空リストは評価されると Lisp の nil となります.ですからここでは,はだかの空リスト()ではなく,評価を抑制するために'()を与えるのが本当かもしれませんが,Lisp の実装において実は空リストとnilはまったく同一で,しかも nilを評価してもnilです.ですから上記表記において'()()も結果はまったく同じことですので,このようにしてもかまいません.Lisp での書き方の作法として,通常グローバル変数には最初と最後にアスタリスクをつける習慣になっていますので,ここでもそうしました.

 さて,状態変数を定義するのに make-statevar を用いるのではなく,静的に見えてしかも定義済みの結果が自動的に *state* に保存されるようなマクロ defstatevar を定義しましょう.

[定義3]

> (defmacro defstatevar (name parms &optional (val t))
    `(car (setq *state*
                (cons (make-statevar :name ',name :parms ',parms :val ',val) *state*))))
defstatevar 

 
 defmacro は Lisp におけるマクロ定義のためのマクロで,一見関数定義のように見えますが,やることはまったく異なります.詳しい説明はそれだけで本が一冊書けるほどですので止めますが,`は backquote と言ってquoteと同じように以下のフォームをそのまま返します.ただしその中にカンマ , があるとカンマ , で修飾された式(上記例ではシンボル)の値をとってきてそれに置き代えたものを返します.ですから上記マクロ定義は defstatevar が呼び出されたとき,name,parms,val に与えられた値が置き換わった形式の (car (setq *state* ... )) が返されるわけです.マクロ呼び出し時に Lisp がする仕事はもう一つあって,その返されたフォームをその文脈の中で実行します.以下の例を見てください.

[例5]

> (defstatevar at(r1 loc1))
#S(statevar :name at :parms (r1 loc1) :val t)
> *state*
(#S(statevar :name at :parms (r1 loc1) :val t))

 
 このように関数呼び出しのときには quote が必要であったものが,マクロを使うと必要でないようにマクロを定義することができます(マクロが quote をつける).この実施例では,確かに例4と同じ結果が得られ,しかもグローバル変数 *state* にリストの要素としてつけ加わっていることがわかります.定義3において,引数 val には optinal 指定がありますが,このような引数は省略することができます.省略した場合には,デフォールトの値が入りますが,定義3ではデフォールト値として t を指定しています.

 教科書 APTP では,状態変数でもその領域においては決して変化しない関係を rigid relation と呼んでいますが,rigid relation とそれ以外の状態変数との記述上の区別は曖昧です.ここでは後での計算の都合上 rigid relation とそれ以外の状態変数をはっきり区別することにします.そのためにまず statevar の副型として rigid-relation を定義します.形は状態変数でもその値は変化しない,特殊な状態変数と理解して下さい.

[定義4]

> (defstruct (rigid-relation (:include statevar)))
relation 
> (defmacro defrelation (name parms &optional (val t))
    `(car (setq *state*
                (cons (make-rigid-relation :name ',name :parms ',parms :val ',val) *state*))))

 
 構造体は自動的に Lisp の型としての定義がされますが,このように :include オプションつきで構造体を定義することで,ある構造体を別の構造体の副型として定義することができます.もちろんスロットを新たに追加することもできますが,ここではrigid-relation のスロットはstatevar と同じとしています.defrelation のマクロは defstatevar とはmake-statevar だけが make-rigid-relation に置き換わっています.ある statevar が rigid-relation かどうかは,述語関数 rigid-relation-p で判定できます.

2.3 オペレータ

 古典的計画問題では,オペレータを name,precond,effects の三つ組みとして定義します.以下の定義では comment も追加しましたが,これは本質的ではありません.オペレータにおける name はさらに名前シンボルと引数から成るとしています.
 オペレータとそのnameを定義し,さらにオペレータの名前シンボルを取ってくる関数と引数をとってくる関数をついでに用意しましょう.

[定義5]

> (defstruct operator name comment precond effects)
operator 
> (defstruct ope-name symbol parms)
ope-name 
> (defun operator-symbol (operator)
    (ope-name-symbol (operator-name operator)))
operator-symbol
> (defun operator-variables (operator)
    (ope-name-parms (operator-name operator)))
operator-variables

 
 オペレータパラメータは precond と effects に現れるすべての変数を集めたものです.precond はこのオペレータを適応するときに成立していなければならない前提条件であり,建前としては状態変数を含む論理式ですが,ここでは単に成立していなけらばならない状態を表す状態変数の集合としています.論理的にはAND結合ですね.その時の状態に対してこの条件のすべての要素が成立しなければなりません. effects はこのオペレータが適応されたときの状態の変化を記述したものです.effects の要素には肯定的条件と否定的条件があり,肯定的条件はそれが状態に追加され,否定的条件は状態からそれにマッチした条件が取り去られます.否定的条件とは状態変数を (not ... ) で囲ったものです.

 さて,ユーザにとって見やすく使いやすいように,オペレータの定義をたとえば次のように実施できるようにしたいとしましょう.

[例6]

> (defoperator move(r l m)
    "robot r moves from location l to an adjacent location m"
    :precond ((adjacent(l m)) (at(r l)) (not (occupied(m))))
    :effects ((at(r m)) (occupied(m)) (not (occupied(l))) (not (at(r l)))))

 
 そのために,まずオペレータ定義用マクロを次のように定義します.

[定義6]

> (defvar *operators* ())
*operators*
> (defmacro defoperator (symbol parms &rest initargs)
  `(car
    (setq *operators*
          (cons (make-operator :name (make-ope-name :symbol ',symbol :parms ',parms)
                               :comment ,(if (stringp (car initargs)) (car initargs))
                               :precond (make-state ',(if (stringp (car initargs))
                                                          (getf (cdr initargs) :precond)
                                                        (getf initargs :precond)))
                               :effects (make-state ',(if (stringp (car initargs))
                                                          (getf (cdr initargs) :effects)
                                                        (getf initargs :effects))))
                *operators*))))

 
 (if (stringp ... )) のフォームには解説が必要かも知れません.たとえば例6の場合には,マクロの引数 symbol には move が,引数 parms には (r l m) が束縛されます.そしてそれ以下の呼び出しの本体,すなわち "robot ..." 以下 (not (at(r l))) までがリスト形式で initargs に束縛されます. そこで(if (stringp ... )) の形式はコメントが文字列として供給されたときとされないときで異なる処置をするようになっているわけで,これでコメントの有無にかかわらず, make-state には期待する正しい形式の引数が渡されます.getf は上記のような属性形式のリストからある属性の値を取り出す Lisp の関数です.

 make-state は状態変数表示のリスト(ただし否定も含む)の各要素に対して make-atom を実施してその結果のリストを返します.前述のように,状態とは状態変数の集合と考えています.

[例7]

> (defun make-state (forms)
    "makes a state from a list of atom-forms."
    (mapcar #'make-atom forms))
make-state
> (make-state '((adjacent(l m)) (at(r l)) (not (occupied(m)))))
(#S(statevar :name adjacent :parms (l m) :val t)
 #S(statevar :name at :parms (r l) :val t)
 #S(statevar :name occupied :parms (m) :val nil))

 
 論理式では,論理結合子や否定のない素となる論理式を原子式,アトムと呼んでいますが,計画問題では"at(r l)"というような状態変数表示で表現される真偽値を有するものがアトムとなります.教科書ではアトムはカンマで区切られて並んでいますが,Lisp のパーザを使ってこの形式をそのまま読み込ませるために,一つのアトムは状態変数表示を必ずリストで括って表現することにします.すなわち ( at(r l) ) となります.
 make-atom は一つのアトム式,たとえば ( at(r l) ),あるいはその否定,たとえば (not ( at(r l) )) を受け取って,対応するアトムを返します.ただしこのときに返される値は,グローバル変数 *state* に追加はされません.オペレータを実際にその時々の *state* に適応することで,*state* の内容が変化していきます.アトムとその否定をリテラルと呼ぶことがあります.

[例8]

> (defstruct (atom-form (:type list)) pred parms eq val)
atom-form
> (defun negate-atom (atom)
    (ecase (statevar-val atom)
      ((t) (setf (statevar-val atom) nil) atom)
      ((nil) (setf (statevar-val atom) t) atom)))
negate-atom
> (defun make-atom (literal)
  "makes and returns a state variable from literal."
  (if (eq (car literal) 'not) (negate-atom (make-atom (second literal)))
    (make-statevar :name (atom-form-pred literal) :parms (atom-form-parms literal) :val t)))
> (make-atom '(at(r l)))
#S(statevar :name at :parms (r l) :val t)

 
 構造体 atom-form の定義はリスト形式での上記アトム式の各要素に対してあたかも構造体のようなアクセス関数を定義するためのものです.(:type list) のオプションでそのようなことになります.詳しくはCommon Lisp のマニュアルを参照してください.関数 negate-atom は一つのアトムすなわちstatevar構造体を受け取って,それの否定,すなわち val が真なら偽に置き換え,偽なら真に置き換えた構造体を返します.関数 make-atom は引数(リストのはず)の最初の要素が not ならば,2番目の要素を make-atom した結果についてその否定を行って,その値を返します.make-atom は再帰していますが,これで (not (not ... )) のように幾重にも重なった not でも正しく処理されます.最初の要素が not でなければ,正規のアトム式で,その場合 make-statevar しますが,そのval の値は t としています.

 さてここで,論理表現からは少し離れますが,ATPTの表現方法に合わせて,アトム式の表現をさらに拡張します.まずはじめに,precond 中には2種類の条件があることに気がつきます.すなわち,オペレータの適応によって変化する条件と,そうでない条件すなわち rigid-relation です.教科書 APTP では,rigid relation とそれ以外の条件との記述上の区別は曖昧ですが,ここでは後での計算の都合上,オペレータ定義における rigid relation とそれ以外の状態変数とを=の有無ではっきり区別することにします.すなわち,=のないアトム式は必ず rigid relation であり,そうでないアトム式はその領域では変化し得る状態変数(そのオペレーションでは変化しないかも知れないが),とします.
 下記の例で言えば,adjacent(l m) は,場所 l と m が隣接している条件を示し,その時に限り,ロボット r の場所 l がこのオペレータ move の適応によって場所 m に変化することを表現します.もちろん adjacent(l m) は変化しません.

[例9]

> (defoperator move(r l m)
    "robot r moves from location l to an adjacent location m"
    :precond ((adjacent(l m)) (rloc(r) = l))
    :effects ((rloc(r) <- m)))

 
 make-atom では =や <- のないアトム式を読み込んだら,それを rigid relation として定義し,そうでなければ statevar として定義するようにします.

[例10]

> (defun make-atom (literal)
  "makes and returns a state variable from literal form."
  (if (eq (car literal) 'not) (negate-atom (make-atom (second literal)))
    (ecase (atom-form-eq literal)
      (=        ; pred(x y) = val, matching to *state*
       (case (atom-form-val literal)
         ((t nil) (make-statevar :name (atom-form-pred literal)
                                 :parms (atom-form-parms literal)
                                 :val (atom-form-val literal)))
         (otherwise
          (make-statevar :name (atom-form-pred literal)
                         :parms (append (atom-form-parms literal) (list (atom-form-val literal)))
                         :val t))))
      (<-       ; pred(x y) <- val, negate old assertion and create new assertion
       (make-statevar :name (atom-form-pred literal) :parms (atom-form-parms literal)
                      :val (atom-form-val literal)))
      ((nil)    ; rel(x y), rigid atom assertion
       (make-rigid-relation :name (atom-form-pred literal) :parms (atom-form-parms literal)
                            :val t))
      )))

 
 オペレータが適応できるのは,precond 中の状態変数すべてが状態 *state* 中の状態変数とマッチした場合です.*state* 中の状態変数はパラメータがすべて:parmsスロットにあり,:valスロットの値は t か nil としています.そのため,precond 中に現れる=の場合には,=で指定される値が t か nil 以外の場合には,それは最後のパラメータとして,その値は t とします.effects 中に現れる <- の場合には,とりあえずそのまま状態変数を作り出しますが,オペレータ適応時にはその記述にしたがって,*state* の内容を書き換えなくてはなりません.すなわち,precond 中の条件に該当する内容は否定され,effects の条件が追加されます.effects 中の <- のあるアトム式からは,状態変数の論理表現としては precond にある条件の否定と,一部パラメータが変更された肯定の二つが出てくるわけで,それを今後実装しなければなりません.

2.4 変数と定数,そしてアクションとグラウンド

 これまでロボット r とか場所 l というシンボルについて,それが変数なのか定数なのかをあまり区別しないで使ってきました.ここでシンボルには型があって(これまで出てきた Lisp の型とは違います.あとでそれが何であるか導入することにします),型のインスタンスを表現するものとしての定数と,型の任意のインスタンスを表現するものとしての変数を区別して使うことにします.APTPでは変数としてのロボットを r ,定数としてのロボットを r1 とか r2 というように表現していますが,我々はもう少し分かりやすく,変数には必ず第1文字に?か$をつけ,それ以外の文字を持つシンボルは定数ということにします.すなわち,変数としてのロボット ?r とか,定数としての r1 とか r2 というようにします.すると与えられたシンボルが変数か定数かはその第1文字で見分けられます.

[定義7]

> (defun variable? (x)
  "Is x a variable (a symbol starting with $ or ?)?"
  (and (symbolp x) (or (eql (char (symbol-name x) 0) #\$)
                       (eql (char (symbol-name x) 0) #\?))))

 
 これまでオペレータの引数に使ってきたものはすべて変数シンボルに置き換える必要があります.そしてオペレータ引数の変数をすべてその変数の型のインスタンスである定数に置き換えたものを,アクションとよびます.オペレータはスキーマみたいな抽象的な定義ですが,アクションはそれをインスタンス化して,ひょっとしたらもう実行できるかのようなものですね.

 ここで重要な概念,グラウンディングを取り上げます.一階述語論理では,変数を持たない項を基底項(ground term)と言います.項とはオブジェクトを指すシンボルと関数のことです.だから,ロボット r1 とか,at(r1) は項ですね.状態変数がパラメータに変数を持たないとき(変な言い回しですが,意味は明快ですね),それはグラウンドしていると言います.
 状態変数ではなく,rigid-relation のときは,そこに含まれるパラメータに変数が含まれないだけでなく,*state* にそれが成立していなければなりません.rigid-relation の定義からして,それが成立していないということは,それが precond に含まれるオペレータは絶対に成立しない,すなわちグラウンディングし得ないということになるからです.

[定義8]

> (defun ground-p (x)
  (etypecase x
    (symbol (not (variable? x)))
    (statevar (notany #'variable? (statevar-parms x)))
    (rigid-relation (and (notany #'variable? (rigid-relation-parms x))
                         (eq (rigid-relation-val x) t)
                         (find x *state* #'equalp)))
    ))

 
 アクションとは,オペレータの変数がすべてインスタンス化されて定数になり,しかも precond に含まれる rigid-relation が成立しているようなもののことです.まさにもうちょっとで,実行可能ですね.以下ではアクションの定義と一緒に,オペレータをアクションに変更するための関数を用意しました.

[定義9]

> (defstruct (action-name (:include ope-name)))
action-name
> (defstruct (action (:include operator)))
action 
> (defun make-action-from (operator bindings)
  "makes an action from operator with substitution by bindings."
  (let ((parms (subst-bindings bindings (operator-variables operator))))
    (when (every #'ground-p parms)
      (let ((precond (subst-bindings bindings (operator-precond operator))))
        (when (every #'ground-p (remove-if-not #'rigid-relation-p precond))
          (make-action :name (make-action-name :symbol (operator-symbol operator)
                                               :parms parms)
                       :comment (operator-comment operator)
                       :precond precond
                       :effects (subst-bindings bindings (operator-effects operator))))))))
make-action-from

 
 make-action-from の引数にはアクションの原型となるオペレータとパラメータにある変数と置き換える定数を組みにした bindings を与えます.この bindings と置き換えの関数 subst-bindings は後ほど,単一化 (unification) のところで定義します.make-action-from ではオペレータのパラメータについて bindings で置き換えを行い,その結果がすべて定数かどうかを確認しています.また,precond についてはそこから rigid-relation だけを抜き出して,それらがすべてグラウンドされているかを確認しています.すべて確認できたときに初めて make-action してその結果を返します.make-action できなかったときには nil が返ります.

2.5 アクションの適応可能性と適応

 アクションの precond 中の肯定的条件が状態 *state* 中にあり,否定的条件が状態 *state* 中になければ(あるいは条件の否定があれば),そのアクションは適応可能 (applicable) といいます.

[定義10]

> (defun applicable-action-p (action state)
    (and (subsetp (positive-precond (action-precond action)) state :test #statevar-equalp)
         (let* ((negatives (negative-precond (action-precond action)))
                (negative-relatives (intersection negatives state
                                                  :test #'pseudo-statevar-equalp))
                (default-negatives (set-difference negatives negative-relatives
                                                   :test #'pseudo-statevar-equalp)))
           (or (null negatives)
               (and (subsetp negative-relatives state :test #statevar-equalp)
                    (null (intersection default-negatives state :test #statevar-equalp))
                    )))))
applicable-action-p
> (defun positive-precond (precond)
    (remove-if-not #'statevar-val precond))
positive-precond
> (defun negative-precond (precond)
    (remove-if #'statevar-val precond))
negative-precond
> (defun pseudo-statevar-equalp (s1 s2)
    "this function does not care of value of statevar."
    (or (equalp s1 s2)
        (and (eq (statevar-name s1) (statevar-name s2))
             (let ((parms1 (statevar-parms s1))
                   (parms2 (statevar-parms s2)))
               (case (statevar-val s1)
                 ((t nil) (every #'equal parms1 parms2))
                 (otherwise (case (statevar-val s2)
                              ((t nil) (every #'equal parms1 parms2))
                              (otherwise (equal parms1 parms2)))))))))
pseudo-statevar-equal
> (defun statevar-equalp (s1 s2)
    "this function can be applied to statevar vs rigid-relation."
    (or (equalp s1 s2)
        (and (eq (statevar-name s1) (statevar-name s2))
             (equal (statevar-parms s1) (statevar-parms s2))
             (equal (statevar-val s1) (statevar-val s2)))))

 
 positive-precond と negative-precond はstatevar のリストとなっている引数 precond から,それぞれ肯定的( precond-val が t ),否定的( precond-val が nil )であるものを取り出してくる関数です.これらを使って applicable-action-p では,仕事をします.関数 pseudo-statevar-equalp は状態変数の名前とパラメータについては等価性を調べますが,その値 val については,等価であろうがなかろうが気にしないようなものです.statevar-equalp はLisp の equalp では statevar と rigid-relation を区別してしまうので,区別無く調べるために用意されたものです.
 applicable-action-p の一行目では肯定的 precond がすべて状態に含まれるかどうかを調べます.もし含まれていなければこの関数はすぐに nil を返します.すべて含まれていたときに否定的 precond についてチェックします.まずはじめに否定的 precond のなかから状態に関連する状態変数だけを取り出して,negative-relatives としておきます.実は Lisp の intersection の仕様では,答えとして返されるものが,その第1引数からくるものか第2引数からくるものかは定義されていません.したがって,Allegro Common Lisp ではない処理系では,ひょっとして第2引数のものが答えに現れるかも知れません.その場合にはこのコードはうまく働かないことになってしまいますが,ここでは少し横着をしてこのままにしておきます.default-negatives には否定的 precond の内でも状態に関連しないものが集められます.さて,否定的 precond が存在しなければ,すぐに applicable として t を返します.否定的 precond がある場合には,状態に関連するものは厳密にすべて否定的でなければならないし,それ以外のものは厳密にすべて状態にあってはならない,というわけです.

 アクションが applicable であれば,実際にそれを適応することができます.ここで適応するという意味は,effects にある肯定的効果を取り出して状態にそれを追加し,否定的効果をとりだして,状態にある関連する肯定的効果を否定的にする,ということです.例3にあるprogress を状態変数を受け入れられるように少しだけ修正します.precond の場合と異なって,effects において肯定的効果や否定的効果をとりだす手続きはちょっと面倒です.その理由は状態変数の表現が関数形式になっていて,値が真偽値ではなく,最後のパラメータ(真偽値としては真)と解釈しなければならない場合があるからです.apply-action が返す値は更新された状態ですが,まだ副作用は何も起こしていないということに注意してください.典型的な呼び出し形式としては *state* を入力に与えて,返される値を *state* にセットするということになります.

[定義11]

> (defun apply-action (action state)
    "returns the new state produced by applying action to the state."
    (progress state
              (positive-effects (action-effects action) (action-precond action))
              (negative-effects (action-effects action) (action-precond action))))
apply-action
> (defun progress (state add-list del-list)
    (union (set-difference state del-list :test #'pseudo-statevar-equalp) add-list
           :test #'statevar-equalp))
progress 
> (defun positive-effects (effects precond)
    (declare (ignore precond))
    (mapcar #'(lambda (ef)
                (case (statevar-val ef)
                  ((t) ef)
                  (otherwise
                   (make-statevar :name (statevar-name ef)
                                  :parms (append (statevar-parms ef) (list (statevar-val ef)))
                                  :val t))))
      (remove-if-not #'statevar-val effects)))
positive-effects
> (defun negative-effects (effects precond)
    (union (remove-if #'statevar-val effects)
           (append (intersection (mapcar #'(lambda (pre) (negate-atom (copy-statevar pre)))
                                   (positive-precond precond))
                                 (remove-if-not #'statevar-val effects)
                                 :test #'pseudo-statevar-equalp)
                   (intersection (mapcar #'(lambda (pre) (copy-statevar pre))
                                   (negative-precond precond))
                                 (remove-if-not #'statevar-val effects)
                                 :test #'pseudo-statevar-equalp))
           :test #'statevar-equalp))
negative-effects

 
 positive-effects は引数 effects から肯定的効果のみを取り出します.このとき第2引数の precond は実は使われません.次の negative-effects の呼び出し形式と合わせるためだけに,引数にされています.positive-effects では effects から statevar-val が nil でないものだけを取り出し,各々について statevar-val が t であれば,その statevar をそのまま,さもなければ statevar-val を最後のパラメータとし,真偽値を t とする statevar を生成して,返します.negative-effects の場合には,effects から statevar-val が nil であるものに加えて,statevar-val が nil でなくても precond 中の肯定的な状態変数と関連するものについては,その否定を作り出して,それらの合併を答えとします.



Author: Seiji Koide. Copyright (c) 2007 Seiji Koide. Sep. 2007