Artificial intelligence planning with Picat

Currently I'm participating in the Coursera course Artificial Intelligence Planning, and this article is my submission to the course's "Creative Challenge" assignment.

Picat (http://picat-lang.org/) is a new logic-based programming language. In many ways, Picat is similar to Prolog, especially B-Prolog, but it has functions in addition to predicates, pattern-matching instead of unification, explicit non-determinism ( ?=> means a non-deterministic goal), and destructive assignment. In this article I'll use the currently latest version Picat 0.8.

Picat has a planning module for declarative solving planning problems.

To solve a planning problem in Picat a programmer needs to define a final predicate for the final state, and an action predicate for possible actions.

The final predicate in its simplest form has only one parameter – the current state – and succeeds if the state is final.

The action predicate usually has several clauses – one for each possible action. The predicate has 4 parameters: current state, new state, action name, and action cost.

Picat's predicate for finding an optimal plan – best_plan – has 2 input parameters: the initial state and the resource limit, and 2 output parameters: the best plan and its cost. To find an optimal plan the system uses iterative deepening depth-first search-like algorithm. If no plan was found and the maximum resource limit was reached, the predicate fails.

Under the hood, Picat's planning module uses tabling (a form of memoization) to convert a tree search through state space to a graph search.

Below are 3 simple examples of using Picat to declaratively solve planning problems.

Short Deadfish Numbers (Code Golf StackExchange) The first example is my solution for the Short Deadfish Numbers problem. The problem is about an esoteric programming language Deadfish. The language has one accumulator (which starts at 0) and 4 commands: i to increment the accumulator, s to square the accumulator, d to decrement the accumulator, and o to output the accumulator's value and a new line character. The problem asks to find the shortest possible sequence of Deadfish commands to output an integer from the range [0, 255] given as the program's parameter. final (( N , N )) => true . action (( N , A ), NewState , Action , Cost ) ? => NewState = ( N , A + 1), Action = i, Cost = 1. action (( N , A ), NewState , Action , Cost ) ? => A ! = 16, A < N , NewState = ( N , A * A ), Action = s, Cost = 1. action (( N , A ), NewState , Action , Cost ) ? => A > 0, NewState = ( N , A - 1), Action = d, Cost = 1. main ([ X ]) => N = X .to_integer(), best_plan(( N , 0), Plan ), printf ( "%w

" , Plan ++ [o]). GitHub: https://github.com/kit1980/sdymchenko-com/blob/master/ai-planning-picat/ShortDeadfishNumbers.pi The state representation for this problem is a pair (goal value, current value) . The state is final when the current value equals to the goal value. The actions – i , s , and d – correspond to the Deadfish commands. The main predicate gets the goal number from the command-line parameters, calls a two-parameter version of the best_plan (which assumes a very high resource limit – 268435455 – and doesn't return the best plan's cost), and prints the best plan plus the o command. Also see much shorter code golf-style version of this program.

Adjusting passwords (IPSC) Adjusting passwords was the first problem from the Internet Problem Solving Contest 2014. The problem asks to find a shortest sequence of keystrokes to convert an already typed string to a goal string. A keystroke can be just a letter a–z, a backspase ( < ), or "enter" ( * ), which effectively erases the whole string. See the problem statement for more details and examples. % Adjusting passwords % http://ipsc.ksp.sk/2014/real/problems/a.html % Picat 0.8 - http://picat-lang.org import planner. final (( Str , Goal )) => Str = Goal . action (( Str , Goal ), NewState , Action , Cost ) ? => append ( _ , [ Action | Str ], Goal ), NewState = ([ Action | Str ], Goal ), Cost = 1. action (( _ , Goal ), NewState , Action , Cost ) ? => NewState = ([], Goal ), Action = '*' , Cost = 1. action (([ _ | Str ], Goal ), NewState , Action , Cost ) ? => NewState = ( Str , Goal ), Action = '<' , Cost = 1. do_case ( Goal , Was ) => RGoal = reverse ( Goal ), RWas = reverse ( Was ), best_plan(( RWas , RGoal ), length ( Goal ) + 1, Plan , _Cost ), printf ( "%s*

" , Plan ). main => T = read_int(), foreach ( _Case_num in 1.. T ) _ = read_line(), Goal = read_line(), Was = read_line(), do_case( Goal , Was ) end. GitHub: https://github.com/kit1980/sdymchenko-com/blob/master/ai-planning-picat/AdjustingPasswords.pi In this program a state is represented as a pair (reversed current string, reversed goal string) . A state is final if both strings are equal. Strings in Picat are represented as linked lists of characters, and for linked lists it's natural to add and delete items from the beginning (head). The problem asks to add and delete from the end, so both the initial string and the goal string are reversed before passing to best_plan . The actions are adding a letter ( a – z ), * , and < . The append predicate and the [ | ] syntax for getting the head and the tail of a list work exactly the same way as in Prolog. The limit for best_plan is the length of the goal string plus 1, because there is an obvious plan to use * to erase the current string and retype all character of the goal string. printf outputs the plan found by best_plan plus the final "enter".