Cognitive_Modelling_With_Mathematical_Logic/Planner Source
SDxWiki

Plan.oz

Compile this file to create the "pickled functor" file Plan.ozf.

In the driver code, create a new planner class with:

  MyPlanner = {New Plan.planner Fluents Actions}

Call member functions like this:

  {MyPlanner exploreActions(Situation)}
functor
import
  FD FS Search
  Explorer
  Inspector
  Space
%  Ozcar
%  System
%  Inspector
%  Browser
export
  Planner
define

  % This function returns a deep copy of its input. The input can be
  % any type data. Undetermined variables are copied as new cells. If
  % an undetermined variable is a finite domain variable, then the
  % copy will be constrained to the current domain of the input
  % variable.
  %
  % This is needed because we sometimes want undetermined variables
  % from the top-level space to participate in constraints in a
  % subordinate computation space, but in general doing so would cause
  % the subordinate computation space to be unstable. So we use this
  % procedure to copy the top-level variable.
  % TODO: Figure out a way to avoid the need for this function. It's
  % incomplete, and it may in fact be impossible to write it to so
  % that it works correctly in the most general cases, esp. in the
  % presence of custom propagators.
  %
  proc {CopyDomains In ?Out}
    if {FD.is In} then
      Out :: {FD.reflect.dom In}
    elseif {FS.var.is In} then
      raise unimpl(debug:"Don't know how to copy FS domains yet.") end
    elseif {Not {IsDet In}} then
      Out = _			% Fresh variable
    elseif {Record.is In} then
      Out = {Record.map In fun {$ F} {CopyDomains F} end}
    else
      Out = In
    end
  end

  % This is basically an "assert-like" function. If the predicate function
  % P applied to value V is false, we raise an exception.
  proc {Require P V}
    if {Not {P V}} then
      raise reqFail("Requirement {"#P#" "#V#"} failed.") end
    end
  end

  % This function checks that D is a valid representation of a finite
  % integer domain. The definition of the form of D is taken directly
  % from the Mozart System Modules reference manual.
  %
  % We use this to verify some of the definition data for fluents.
  fun {IsFDSpec D}
    fun {IsSimpleSpec D}
      fun {IsRangeDesc D}
	fun {IsFDInt N}
	  {IsDet N} andthen
	  {IsInt N} andthen N >= FD.inf andthen N =< FD.sup
	end
      in
	{IsDet D} andthen
	case D
	of Min#Max then {IsFDInt Min} andthen {IsFDInt Max}
	[] N then {IsFDInt N}
	else false
	end
      end
    in
      {IsDet D} andthen
      case D
      of nil then true
      [] S|Dr then {IsRangeDesc S} andthen {IsSimpleSpec Dr}
      [] S then {IsRangeDesc S}
      else false
      end
    end
  in
    {IsDet D} andthen
    case D
    of compl(S) then {IsSimpleSpec S}
    [] S then {IsSimpleSpec S}
    else false
    end
  end

  % This function returns a function which, when applied to a procedure
  % argument, returns true if the procedure has arity N. (I.e. takes N
  % arguments. We use this in conjunction with Require to verify data
  % definitions for the planner.
  fun {IsProcArity N}
    fun {$ P}
      {IsDet P} andthen {IsProcedure P} andthen {ProcedureArity P} == N
    end
  end

  % FluentDefParsers take a fluent definition in the form of a record
  % and returns a properly-formed fluent definition. The input fluent
  % definition may omit certain fields, which will be filled in with
  % defaults. Calculating the defaults up front allows the planner to
  % work with predictably-formed fluent records, which simplifies
  % the planner code.
  %
  % We also validate the input fluent definition. Any errors cause an
  % exception to be raised. The thrown value is a record with label
  % badFluentDef.
  proc {MkFluent Def ?F}
    % FluentDefParsers is a record of procedures used to generate the
    % corresponding features of a fluent record from the fluent
    % definition.
    FluentDefParsers =
    fdp(name:	fun {$ Def} {Require IsAtom Def.name} Def.name end
	desc:	proc {$ Def ?V}
		  V={CondSelect Def desc {AtomToString Def.name}}
		  {Require IsString V}
		end
	init:	fun {$ Def}
		  Temp={CondSelect Def init FD.inf#FD.sup}
		in
		  if {IsFDSpec Temp} then fun {$} {FD.int Temp} end
		  else
		    {Require {IsProcArity 1} Temp}
		    Temp
		  end
		end
	infer:	proc {$ Def ?V}
		  % If infer is not defined, default to "true"
		  Infer={CondSelect Def infer true}
		in
		  {Require IsDet Infer}
		  if {IsProcedure Infer}
		    andthen {ProcedureArity Infer} == 2 then
		    V=Infer
		  else
		    {Require IsBool Infer}
		    if Infer then
		      % If infer is true, provide a function which copies
		      % the value from the previous situation.
		      V=fun {$ SPrev} {Value.'.' SPrev Def.name} end
		    else
		      % Otherwise, provide a do-nothing funtion.
		      V=proc {$ _ _} skip end
		    end
		  end
		  {Require {IsProcArity 2} V}
		end)
  in
    % Supply defaults and possible calculated values.
    % Everything except the name can be defaulted.
    try
      F = {MakeRecord fluent [name desc init infer]}
      {Record.forAllInd F fun {$ L } {FluentDefParsers.L Def} end}
    catch reqFailed(Msg)
    then
      raise badFluentDef(def:Def debug:{VirtualString.toString Msg}) end
    [] E then
      raise badFluentDef(def:Def debug:E) end
    end
  end

  % This procedure is similar to MkFluent, but for action records.
  proc {MkAction Def ?A}
    % ActionDefParsers is a record of procedures used to generate the
    % corresponding features of a fluent record from the fluent
    % definition.
    ActionDefParsers =
    adp(name:		fun {$ Def} {Require IsAtom Def.name} Def.name end
	desc:		proc {$ Def ?V}
			  V={CondSelect Def desc {AtomToString Def.name}}
			  {Require IsString V}
			end
	pre:		proc {$ Def ?V}
			  V={CondSelect Def pre fun {$ _} true end}
			  {Require {IsProcArity 2} V}
			end
	effect:		fun {$ Def}
			  {Require {IsProcArity 2} Def.effect} Def.effect
			end
	effector: 	fun {$ Def}
			  {Require {IsProcArity 2} Def.effect}
			  Def.effect
			end
	inputs:		proc {$ Def ?V}
			  V={CondSelect Def inputs fun {$ _} var() end}
			  {Require {IsProcArity 2} V}
			end
	)
  in
    try
      A = {MakeRecord action [name desc pre effect effector inputs]}
      {Record.forAllInd A fun {$ L } {ActionDefParsers.L Def} end}
    catch reqFailed(Msg)
    then
      raise badFluentDef(def:Def debug:{VirtualString.toString Msg}) end
    [] E then
      raise badFluentDef(def:Def debug:E) end
    end
  end

  % This procedure makes a fresh situation record from a previous situation
  % and a list of well-formed fluent records.
  proc {MkSitRec SitPrev Fluents ?Sit}
    Sit = {MakeRecord
	   situation
	   prev|{Map Fluents fun {$ fluent(name:N ...)} N end}}
    Sit.prev = SitPrev
    {ForAll Fluents
     proc {$ fluent(name:N init:Init ...)}
       Sit.N = {Init}
     end}
  end

  % Given lists of well-formed fluent and action records, and a
  % situation record, this function returns a procedure that can
  % be used to search for "decision records" that reflect an
  % action choice, action input parameter choices, and an expected
  % consequent situation. The function computes and returns the decision
  % record as its sole argument, thus it conforms to the expectations
  % of the Mozart's constraint-solving system for a solver script.
  fun {MkActionChooser Fluents Actions Sit}

    % This is the procedure that we return. It will have access to the
    % fluents, actions, situation that were passed into
    % MkActionChooser.  You can think of this as being similar to
    % instantiating a template in C++, except that it happens at
    % run-time, and we return the resulting procedure as a first-class
    % value.
    proc {$ R}

    % This procedure helps us track our progress for debugging
    % purposes.
      proc {NoteProgress Str}
	Temp
      in
	if {IsVirtualString Str} then
	  Temp = {VirtualString.toString Str}
	else
	  Temp = Str
	end
	{Assign R.phase {StringToAtom Temp}}
      end
      
      % Here we take a list of fluents, and apply their inference rule
      % speculatively to the current situation. This is "speculative"
      % because if a rule conflicts with what we already know about a
      % fluent, then we simply ignore that rule. This can happen, for
      % instance, if we are trying to infer the state of a fluent in a
      % hypothetical situation after we've applied an action that
      % affected the fluent. The inference rules are simply intended
      % to reflect what we think will happen to a fluent in the
      % absence of any active influences.
      proc {InferFluents Sit Fs}
        % Infer current fluents from prev, speculatively.
	case Fs
	of nil then skip
	[] F|Fr then
	  {NoteProgress "Inferring fluent "#F.name}
	  % Try to apply each of the inference axioms in a temporary
	  % child space. If the space fails, just ignore it. Otherwise
	  % merge the results into this space.
	  local
	    S= {Space.new
		proc {$ _} {Value.'.' Sit F.name} = {F.infer Sit.prev} end}
	  in
	    case {Space.askVerbose S}
	    of failed then skip
	    [] succeeded(...) then _={Space.merge S}
	    [] suspended(...) then _={Space.merge S}
	    else
	      raise
		error(
		  debug: {VirtualString.toAtom
			  "Fluent inference proc for "
			  #F.name#" caused bad space state."})
	      end
	    end
	  end
	  {InferFluents Sit Fr}
	end
      end

      % This is an index into the action list. Here we define its
      % domain to span from one to the length of the action list. At
      % some point in the algorithm below, we distribute the solution
      % search on the range of this variable.
      AIndex = {FD.int 1#{Length Actions}}
      
    in
      % Build a new root record. Initially, the consequent situation
      % is just a default situation, with no further information known
      % about it. The "phase" field is used to track progress, it can
      % be seen conveniently in the Inspector when working with the Explorer.
      R = decision(
	    phase:     {NewCell "Starting"}
	    situation:  {CopyDomains Sit}
	    consequent: _
	    action:     _
	    inputs:     _)
      R.consequent = {MkSitRec R.situation Fluents}

      % Try to apply our inference rules to fill in more information about
      % the current situation of the world. This is how we guess at what
      % we don't know. Overly ambitious attempts to guess at fluent states
      % could rule out actions that would actually be OK, but leaving
      % fluents too undetermined could cause us to investigate or even choose
      % actions that have a low chance of actually succeeding.
      {NoteProgress "Inferring fluents."}
      {InferFluents R.situation Fluents}

      % Now choose an action. We simply distribute on the possible values
      % for AIndex. 
      {NoteProgress "Distributing on action index."}
      {FD.distribute naive [AIndex]}
      R.action={Nth Actions AIndex}

      % Now that we've selected an action, assert that it's precondition
      % axiom is true (1). If not, then this thread (and its computation
      % space) will fail.
      {R.action.pre R.situation} =: 1
      {NoteProgress "Action precondition passed."}

      % Now get the inputs for this action. Some of these may simply
      % be fluents, some may be entirely new values that are for all
      % intents and purposes fluents, but are only created when we're
      % considering the associated action. (This keeps the number of
      % fluents in the model down to a bearable number, hopefully.)
      % The "inputs" procedure of the action is responsible for
      % creating a record containing all of the inputs, so it has wide
      % latitude in deciding how to choose and constrain them. All
      % inputs must be finite-domain variables.
      R.inputs = {R.action.inputs R.situation}
      {NoteProgress "Action inputs defined."}

      % Now we distribute on the inputs. Execution after the distribution
      % proceeds in alternate computation spaces with distinct combinations
      % of input values. Depending on constraints set up by the action
      % record's input procedure, some combinations may be illegal and will
      % cause failure of those spaces. We then actually apply the action's
      % effect axiom to the hypothetical situation and the current combination
      % of inputs to compute the resulting hypothetical situation.
      {NoteProgress "Distributing on action inputs."}
      {FD.distribute naive R.inputs}
      {R.action.effect R.consequent R.inputs}
      {NoteProgress "Applied action effect axiom."}
    end
  end

  % The Planner class is a convenient encapsulation of all the dirty
  % details in this module. On creation of a Planner object, you must
  % supply a list of fluent and action definitions, which will be
  % validated. Then you can use the planner to investigate plans
  % for various situations.
  %
  % TODO: Provide better debugging support by integrating better with
  % the Explorer. Provide for a user-supplied solution evaluation
  % procedure to direct branch-and-bound search strategies.
  class Planner
    feat
      fluents
      actions
    meth init(F A)
      self.fluents = {Map F MkFluent}
      self.actions = {Map A MkAction}
    end
    meth mkSitRec(SitPrev ?Sit)
      Sit = {MkSitRec SitPrev self.fluents}
    end
    meth chooseAction(Sit ?D)
      [D] = {Search.base.one {MkActionChooser self.fluents self.actions Sit}}
    end
    meth exploreActions(Sit)
      {Inspector.configure widgetTreeFont font(family: courier
					       size: 10
					       weight: normal)}
      {Inspector.configure widgetTreeDisplayMode false}
      {Explorer.object script({MkActionChooser self.fluents self.actions Sit})}
    end
  end
end