no way to compare when less than two revisions

Differences

This shows you the differences between two versions of the page.


Previous revision
Next revision
tutorials:intermediate:json_prolog [2019/04/16 12:37] – fine tuning formatting hawkin
Line 1: Line 1:
 +====== Using JSON Prolog to communicate with KnowRob (v0.7.0) ======
 +
 +The following tutorial will show how to query the knowledge base, KnowRob, for information using Prolog queries within LISP and CRAM, using the json-prolog package, and also how to process and extract the data from the gained results. 
 +
 +==== Prerequisites ====
 +
 +We will assume that the previous tutorials have already been completed, and therefore CRAM, roslisp_repl and ROS have been installed. 
 +Make sure you have **Java 8** and **Gradle 5.1** already installed. Otherwise you won't be able to build KnowRob. Other versions could work, but have not been tested yet, so use them at your own risk! 
 +
 +
 +=== Installation ===
 +
 +We need to install KnowRob. For that, please follow the tutorial on the [[http://www.knowrob.org/installation|knowrob website]] The **basic** KnowRob installation is enough. \\
 +//Note:// make sure to select the right branch for your current ROS version.
 +
 +=== Installation troubleshooting ===
 +If it complains about missing **swi-prolog**, you can install it via:
 +<code bash>
 +    $ sudo apt-get install swi-prolog
 +</code>
 +
 +Make sure however, that it is **version 7**. There are major problems with locations of libraries with version 8, which is the version suggested by the swi-prolog website. apt-get should install version 7 by default. To make sure, you can check it with: 
 +<code bash>    
 +    $ dpkg -s swi-prolog
 +</code>
 +
 +=== Launch ===
 +In addition to a **roscore** and the **roslisp_repl** you need to launch **json_prolog** in a Terminal with the following command: 
 +<code bash>
 +    $ roslaunch json_prolog json_prolog.launch 
 +</code>
 +
 +=== CRAM dependencies ===
 +In order for us to be able to communicate with KnowRob from LISP, we need to load the **cram-json-prolog** package in our repl, since it implements the necessary interface for the CRAM to KnowRob communication. We also need **cram-utilities** package in order to process lazy lists. (//Note:// Lazy lists will be explained later in the tutorial. If you are curious and want to jump right there, you can do so by clicking [[json_prolog#Lazy lists | here]].) \\
 +In order to load the packages, do the following in your repl:
 +
 +Open the Emacs command prompt with **,** and type **ros-load-system**. Hit **enter**. It will ask for you to enter the package you want to load, in which case you type **cram_json_prolog**. Confirm with enter. After a few seconds you should see **compilation finished** at the bottom line of Emacs. Repeat the process for **cram_utilities** as well.
 +
 +If you want to add these dependencies directly into your package already, you can add them to your .asd file, as ''cram-json-prolog'' and ''cram-utilities''
 +
 +If you don't have a robot description currently published (and get thrown an error for that by Emacs), use the following instead: 
 +<code lisp>
 +    CL-USER> (roslisp:start-ros-node "my-node")
 +</code>
 +
 +===== Cram-json-prolog =====
 +
 +There are multiple ways to call a query, depending on if one needs only one result or all possible results. We will go through them in the following, explaining the differences between them. In general, it helps if one is familiar with Prolog, since essentially everything that can be done in Prolog, can be done via cram-json-prolog as well. Some Prolog tutorials are provided here: [[http://lpn.swi-prolog.org/lpnpage.php?pageid=online|Prolog tutorial]].
 +
 +==== prolog-simple-1 ====
 +The simplest way is using the ''prolog-simple-1'' function, which gets the query as a string parameter. Any query you could use within Prolog, you can use within this function. It can be used in the following way: 
 +<code lisp>
 +    CL-USER> (json-prolog:prolog-simple-1 "member(X, [1,2,3])")
 +</code>
 +
 +The result in this case is: 
 +<code lisp>
 +    CL-USER> (((?X . 1)))
 +</code>
 +
 +X being the variable we asked Prolog to fill with a value. Whenever Prolog returns a value for a variable, the variable name gets prefixed with a question mark. "1" is the first value in the list we passed on to the ''member'' function of Prolog. The ''member'' function is true if the element is a member of the given list, or in this case since it is a variable, it will return the first element of the list.
 +In a [[json_prolog#Some more information about Prolog in LISP | later section]] we will look in more detail into the bindings we get as a result from Prolog and explain a bit more in depth why we get these results.
 +
 +==== prolog-simple ====
 +Prolog simple works the same way as ''prolog-simple-1'' does, but instead of getting just one result, we get multiple in a lazy-list (lazy-lists will be explained below). 
 +<code lisp> 
 +    CL-USER> (json-prolog:prolog-simple "member(X, [1,2,3])")
 +      (((?X . 1))
 +         . #S(CRAM-UTILITIES::LAZY-CONS-ELEM
 +             :GENERATOR #<CLOSURE (LAMBDA () :IN JSON-PROLOG::PROLOG-RESULT->BDGS)
 +                {1005F52FAB}>))
 +</code>    
 +    
 +The result is basically still the same, but it looks a little bit more complex on the first glance. What we get is a list which contains two elements. The first one is a list in a list with two elements which we already know from the previous call with ''prolog-simple-1'' and which were explained previously, which describe the value Prolog found for '' ?X ''. The second element of the list is  ''#S(CRAM-UTILITIES::LAZY-CONS-ELEM :GENERATOR #<CLOSURE (LAMBDA () :IN JSON-PROLOG::PROLOG-RESULT->BDGS)
 +                {1005F52FAB}>))'' which is a lazy list element generator. (We will explain lazy lists in the next section.)
 +
 +==== lazy lists ====
 +The results we obtain from the queries are stored in lazy lists, meaning the information is loaded on demand, resulting in us seeing only one solution of the query. If we want to see all the results, we can expand the list and force LISP to show all of them to us, for example, by using:
 +
 +<code lisp>
 +    CL-USER> (setq *our-list* (json-prolog:prolog-simple "member(X, [1,2,3])")
 +      (((?X . 1))
 +         . #S(CRAM-UTILITIES::LAZY-CONS-ELEM
 +             :GENERATOR #<CLOSURE (LAMBDA () :IN JSON-PROLOG::PROLOG-RESULT->BDGS)
 +                {1005F52FAB}>))
 +                
 +    CL-USER> (cut:force-ll *our-list*)
 +         (((?X . 1)) ((?X . 2)) ((?X . 3)))
 +</code>
 +We save our list in a variable for convenience purposes. This allows us also to expand the so stored list, and we can see all the possible values Prolog found for the variable ''?X'', which are ''1,2,3''.
 +
 +If we do the same with ''prolog-simple-1'' we can see the difference between ''prolog-simple-1'' and ''prolog-simple'' better:
 +<code lisp>
 +    CL-USER> (setq *our-list* (json-prolog:prolog-simple-1 "member(X, [1,2,3])"))
 +        (((?X . 1)))
 +    CL-USER> (cut:force-ll *our-list*)
 +        (((?X . 1)))
 +</code>
 +
 +In the first case, meaning ''prolog-simple'', we see that all possible bindings for ''X'' are contained within the lazy list, all of them being lists themselves, mapping the variable to a possible value. While for ''prolog-simple-1'' even after expanding the list, the result is still the same, since it returns only one possible value for ''?X'' to begin with.  
 +    
 +==== Accessing data from Prolog ====
 +Now that we have a lazy list with interesting information, how can we access the elements contained within?
 +    
 +We can use ''(cut:lazy-car *our-list*)'' to access the elements of the list. It is used the same way the normal ''car'' is. Same goes for ''cdr''. But there is a better way of doing this. However, from the query, we first need to obtain the lazy list. We can do this in the following way: 
 +<code lisp>
 +    CL-USER> (setq *our-list*
 +                   (cut:lazy-car
 +                    (json-prolog:prolog-simple "member(X, [1,2,3])")))
 +</code>    
 +
 +This gives us the lazy list. Then we can use ''(cut:var-value VAR LIST)'' to access the value of a specific item. In our case we asked for possible values of the variable X. Therefore:
 +<code lisp>
 +    CL-USER> (cut:var-value '?X *our-list*)
 +         1
 +</code>     
 +Gives us the result ''1'', since this is the value of ''X''.
 +That way, one can extract the necessary information, adjust it if needed, and pass it on to the plans which need it. 
 +
 +You can also see the pattern here probably. Basically, any operation one can perform on a list in LISP, has an equivalent in the ''cut'' package with the word ''lazy'' as a prefix. e.g. ''mapcar'' -> ''cut:lazy-mapcar'', ''dolist'' -> ''cut:lazy-dolist'', ''car'' -> ''cut:lazy-car'' etc.
 +
 +It also good practice to add the package the prolog-simple call is used in as a parameter. So instead of the above, use:
 +<code lisp>
 +    CL-USER> (json-prolog:prolog-simple "member(X, [1,2,3])" :package :NICKNAME-OF-YOUR-PACKAGE)
 +</code>
 +
 +==== Importing a package with queries ====
 +There are many packages within KnowRob which define different kinds of queries, depending on what kind of data one is working with and what one wants to achieve. Those packages are very useful, and can be imported, so that their queries can be used as well. A package can be imported by doing the following:
 +
 +<code lisp>
 +
 +    CL-USER> (json-prolog:prolog-simple-1 "register_ros_package(knowrob_common)")
 +       (NIL
 +        . #S(CRAM-UTILITIES::LAZY-CONS-ELEM
 +             :GENERATOR #<CLOSURE (LAMBDA () :IN JSON-PROLOG::PROLOG-RESULT->BDGS)
 +                          {1006444B3B}>))
 +    
 +</code>
 +    
 +This allows us to use the **knowrob_common** package, which allows us to access many utility functions and also the most commonly used queries of KnowRob.
 +
 +With this imported package we could now do the following:
 +
 +<code lisp>
 +
 +    CL-USER (json-prolog:prolog-simple "lowercase('HELLOWORLD',Result)")
 +       (((|?Result| . |''helloworld''|))
 +        . #S(CRAM-UTILITIES::LAZY-CONS-ELEM
 +              :GENERATOR #<CLOSURE (LAMBDA () :IN JSON-PROLOG::PROLOG-RESULT->BDGS)
 +                           {100659E4AB}>))
 +</code>
 +
 +This query converts the first elements upper case letters into lower case, and stores the obtained result in the ''Result'' variable.
 +
 +An overview of the currently available packages and their queries can be found [[http://www.knowrob.org/api | here]]. If when loading a package Emacs only returns ''NIL'' then it might be that the package does not exist or is not found on your system. In such cases, you can check the terminal in which json-prolog was launched. If it cannot be found, you will see the following error: 
 +<code bash>
 +   [rospack] Error: package 'knowrob_maps_tools' not found
 +   [librospack]: error while executing command
 +</code>
 +
 +If you call a query for which the package is not loaded yet, Emacs will throw the following error:
 +    
 +<code lisp>
 +
 +    Prolog query failed: PrologException: error(existence_error(procedure, '/'(u_load_episodes, 1)), context(':'(system, '/'(call, 1)), _1))
 +    [Condition of type SIMPLE-ERROR]
 +    
 +</code>
 +    
 +and if you check your terminal, in which the **json_prolog** node is running, there will be a huge java print as well, basically explaining the same thing.
 +  
 +    
 +==== Some more information about Prolog in LISP ====
 +    
 +If we want to check, if a certain number is member of the list, we must declare the variable accordingly. e.g.
 +
 +<code lisp>
 +    CL-USER> (json-prolog:prolog-simple-1 "member(2, [1,2,3])")
 +</code>
 +
 +and while one would expect Prolog to return "true", in json-prolog what we get is 
 +<code lisp>
 +    (NIL)
 +</code>
 +
 +Meaning a list with only one element, the element being NIL. Since in LISP everything that is not NIL, is ''true'', the answer we get here means ''true'', since it's a ''list'' of NIL and not NIL. If one were to test this out within the Prolog console itself, the result would be "true". We can actually test this with json-prolog as well, to prove our assumption.
 +<code lisp>
 +    CL-USER> (json-prolog:prolog-simple-1 "true")
 +    (NIL)
 +    
 +    CL-USER> (json-prolog:prolog-simple-1 "false")
 +    NIL
 +</code>
 +
 +We can note that if the result is ''true'' we get returned a list, even if NIL is the only member of this list. If the result is ''false'', we just get the element NIL as a result, which is LISPs way of saying "false".
 +
 +The question is, why is that so?
 +
 +
 +The json-prolog returns a list of all possible bindings that can be true for the Prolog query. For example,
 +<code lisp>
 +    CL-USER> (cut:force-ll (json-prolog:prolog-simple "member(X, [1,2]), Y = 3."))
 +</code>
 +returns a list of bindings for X and Y (Note: ''force-ll'' expands the lazy list we get returned from Prolog. ''cut'' is the nickname of the ''cram-utilities'' package that one might need to import if this was not done prior):
 +<code lisp>
 +    (((?X . 1) (?Y . 3)) ((?X . 2) (?Y . 3)))
 +</code>
 +whereas
 +<code lisp>
 +    CL-USER> (json-prolog:prolog-simple "member(1, [1,2]), 3 = 3.")
 +</code>
 +
 +doesn't have any variables that it needs to bind so it returns a list of bindings -- bindings do not exist -- so therefore we get a list of NIL.