🤖 robotics

Learning KnowRob

date
slug
knowrob
author
status
Public
tags
knowrob
ros1
robotics
krr
ros
summary
type
Post
thumbnail
category
🤖 robotics
updatedAt
Jun 13, 2024 10:02 AM

Learning KnowRob

This post is a summary of my notes/takes while learning KnowRob.

What is KnowRob?

A brief description of my understanding of what KnowRob is:
KnowRob is a knowledge processing system consisting of a knowledge base (KB), knowledge management interfaces, and reasoning mechanisms.
In practical terms, the knowledge base is represented as an OWL ontology, which internally gets converted to a triple format and stored in a MongoDB database. Knowledge management is performed with custom KnowRob Prolog predicates, which can be queried through a KnowRob command line interface or through code in ROS nodes. The reasoning process is carried out with user-defined Prolog rules when the KB is queried, the inferred facts are virtualized, that is, they are available only at the time of the query and are not stored in the KB.
 
To fully understand what KnowRob is, check the KnowRob papers, its website, and KnowRob’s repo README.
If you are going for the papers, I recommend checking these 2 first:

Installing KnowRob

At the time of this writing (10/08/2023), KnowRob's master branch is intended to be used with Ubuntu 20.04 and ROS noetic. Since for my work I am already using ROS 2, I have Ubuntu 22.04 in my work laptop. Thus, to use KnowRob, I need to run it with docker.
There is a dockerfile and a docker-compose file in the KnowRob repository, however it is outdated. So, I created the repository knowrob_docker to store an updated version of it, and I added a CI to rebuild the image every week and update the docker image registry.
You can either use the docker image in the registry ghcr.io/rezenders/knowrob_docker:main or you can build the image yourself. If you choose to build it, just follow the instructions in the README, and replace the image name in the docker-compose file.

Running KnowRob

To run KnowRob, you need to start roscore, the KnowRob node, any additional nodes that should interact with KnowRob, and mongodb.

Docker

If you are using docker with the dockerfile I provide, you can start roscore + KnowRob + mongodb by starting the docker_compose file:
docker-compose up 
Note: Don’t forget to install docker and docker-compose
Then you need to run any additional nodes within a docker container connected to the knowrob_network. An example:
docker run -it --rm --net knowrob_network --name rosprolog_cli --env ROS_MASTER_URI=http://knowrob:11311 ghcr.io/rezenders/knowrob_docker:main rosrun rosprolog rosprolog_commandline.py
Note: This docker image only contains KnowRob, if you want to use it with your own custom package, you need to create a new image from this one. Check these examples of a custom dockerfile and docker-compose.yml

Bare metal (without docker)

MongoDB:
sudo systemctl start mongod.service
roscore:
roscore
Knowrob:
roslaunch knowrob knowrob.launch
Additional nodes, example:
rosrun rosprolog rosprolog_commandline.py
Note: In the rest of the post I am only going to provide instructions on how to run with docker, just adapt the commands if you are using it bare metal

Example of how to use KnowRob

To exemplify how KnowRob can be used, I will use the content of the knowrob_intro repository.
A snippet of one of the OWL ontologies contained in the repository can be seen below:
Snippet of the ontology from the knowrob_intro repo
Snippet of the ontology from the knowrob_intro repo
So given this ontology, how can we use KnowRob to discover:
  1. Which types of food are perishable?
  1. Which types of food are not perishable?
  1. What types of Robot it contains?
  1. Is Honey non perishable?
  1. What are the instances of Food?
  1. What is the weight of a certain instance of Milk?
These can be discovered by querying the knowledge base, which in KnowRob is done with custom Prolog queries. Let’s check how we can do this below.
Note: In the snippet above everything is an Entity, no Individuals are shown

Start KnowRob with the knowrob_intro package

To begin with this example, we need to start KnowRob, mongoDB, and the rosprolog command line interface using the docker image from the knowrob_intro repository.
First, clone the knowrob_intro repo:
cd ~/
git clone https://github.com/kas-lab/knowrob_intro.git
Then, start KnowRob + mongoDB:
cd ~/knowrob_intro
docker-compose up
In another terminal, start the prolog command line:
docker run -it --rm --net knowrob_network --env ROS_MASTER_URI=http://knowrob:11311 ghcr.io/kas-lab/knowrob_intro:main rosrun rosprolog rosprolog_commandline.py

Query using rosprolog command line interface

Now, we can load an ontology and start interacting with it.
In the prolog command line terminal, run:
load_owl('package://knowrob_intro/owl/krr_exercise.owl', [namespace(pap, 'http://www.airlab.org/tiago/pick-and-place#')])
This command loads the krr_exercise.owl ontology into knowrob with the namespace pap. If everything worked, this command should have returned true.
Now we can query the KB to find the answers for the questions above.
Question 1: Which types of food are perishable?
To find an answer for this question, we can run the following query in the prolog terminal:
subclass_of(P, pap:'Perishable')
You should get the following result:
P: http://www.airlab.org/tiago/pick-and-place#Cheese ;

P: http://www.airlab.org/tiago/pick-and-place#Milk ;

P: http://www.airlab.org/tiago/pick-and-place#Yogurt.
If we compare this result with the figure showing a snippet of the ontology, we can see that the answer is correct.
Note: You need to press ENTER to get the next result, in this case you will need to press it 3 more times.
Question 2: Which types of food are not perishable?
?- subclass_of(P, pap:'NonPerishable').
P: http://www.airlab.org/tiago/pick-and-place#Hagelslag ;

P: http://www.airlab.org/tiago/pick-and-place#Honey ;

P: http://www.airlab.org/tiago/pick-and-place#Rice.
Question 3: What types of Robot it contains?
?- subclass_of(P, pap:'Robot').
P: http://www.airlab.org/tiago/pick-and-place#Tiago.
Question 4: Is Honey non perishable?
?- subclass_of(pap:'Honey', pap:'NonPerishable').
true.
Question 5: What are the instances of Food?
?- instance_of(X, pap:'Food').
X: http://www.airlab.org/tiago/pick-and-place#hagelslag1 ;

X: http://www.airlab.org/tiago/pick-and-place#honey1 ;

X: http://www.airlab.org/tiago/pick-and-place#milk1 ;

X: http://www.airlab.org/tiago/pick-and-place#rice1 ;

X: http://www.airlab.org/tiago/pick-and-place#yogurt1._
Question 6: What is the weight of a certain instance of Milk?
From the result of question 5, we know that there is an individual of Milk named milk1. Let’s find out what is the weight of this individual.
To get milk1's weight, we can query its hasMassAttribute property
?- triple(pap:'milk1', soma:'hasMassAttribute', X).
X: http://www.airlab.org/tiago/pick-and-place#weight_value_milk.
With this query, we discovered that there is an individual weight_value_milk that holds milk1's weight. We can query its hasMassValue to finally find out its weight
?- triple(pap:'weight_value_milk', soma:'hasMassValue', X).
X: 0.7.
Add new knowledge to KB
To add knowledge you can use the kb_project predicate.
For example, to add milk2 as an instance of Food:
?- kb_project(instance_of(pap:'milk2', pap:'Food')).
true.
?- instance_of(X, pap:'Food')
X: http://www.airlab.org/tiago/pick-and-place#hagelslag1 ;

X: http://www.airlab.org/tiago/pick-and-place#honey1 ;

X: http://www.airlab.org/tiago/pick-and-place#milk1 ;

X: http://www.airlab.org/tiago/pick-and-place#rice1 ;

X: http://www.airlab.org/tiago/pick-and-place#yogurt1 ;

X: http://www.airlab.org/tiago/pick-and-place#milk2.
Add mass attribute to milk2:
?- kb_project(instance_of(pap:'mass_milk2', soma:'MassAttribute')).
true.
?- kb_project(triple(pap:'mass_milk2',soma:'hasMassValue', 0.7)).
true.
?- kb_project(triple(pap:'milk2', soma:'hasMassAttribute', pap:'mass_milk2')).
true.
You can query the KB to check if everything went well:
?- instance_of(M, soma:'MassAttribute')
M: http://www.airlab.org/tiago/pick-and-place#milk_weight_full ;

M: http://www.airlab.org/tiago/pick-and-place#rice_weight_half ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_hagelslag ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_honey ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_milk ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_yogurt ;

M: http://www.airlab.org/tiago/pick-and-place#mass_milk2.

?- triple(pap:'mass_milk2', soma:'hasMassValue', X).
X: 0.7.

?- triple(pap:'milk2', soma:'hasMassAttribute', X).
X: http://www.airlab.org/tiago/pick-and-place#mass_milk2.
Remove knowledge from KB
To remove knowledge you can use the kb_unproject predicate
To remove milk2 and its mass attribute:
?- kb_unproject(triple(pap:'milk2', soma:'hasMassAttribute', pap:'mass_milk2'))
true.
?- kb_unproject(triple(pap:'mass_milk2',soma:'hasMassValue', 0.7)).
true.
?- kb_unproject(triple(pap:'mass_milk2', rdf:'type', soma:'MassAttribute')).
true.
?- kb_unproject(triple(pap:'milk2', rdf:'type', pap:'Food')).
true.
You can query the KB to check if everything was deleted correctly:
?- triple(pap:'milk2', soma:'hasMassAttribute', X).
false.

?- triple(pap:'mass_milk2', soma:'hasMassValue', X).
false.

?- instance_of(M, soma:'MassAttribute')
M: http://www.airlab.org/tiago/pick-and-place#milk_weight_full ;

M: http://www.airlab.org/tiago/pick-and-place#rice_weight_half ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_hagelslag ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_honey ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_milk ;

M: http://www.airlab.org/tiago/pick-and-place#weight_value_yogurt.

?- instance_of(X, pap:'Food')
X: http://www.airlab.org/tiago/pick-and-place#hagelslag1 ;

X: http://www.airlab.org/tiago/pick-and-place#honey1 ;

X: http://www.airlab.org/tiago/pick-and-place#milk1 ;

X: http://www.airlab.org/tiago/pick-and-place#rice1 ;

X: http://www.airlab.org/tiago/pick-and-place#yogurt1.
Note:
To add a new instance of a class we used kb_project/1 + instance_of/2, e.g., kb_project(instance_of(M, soma:'MassAttribute')).
However, kb_unproject/1 doesn’t work with instance_of/2, thus, we need to use it in combination with triple(instance, rdf:'type', class), e.g., kb_unproject(triple(pap:'milk2', rdf:'type', pap:'Food')).

Extra predicates for querying

In the example above, we showed how some predicates can be used to query the KB, such as subclass_of, instance_of, and triple. However, there are many more predicates available. I have to confess that I didn’t find out if there is a page containing ALL predicates available, but you can check some other predicates in the pages:

Query via code in ROS nodes

To query KnowRob with code, we need to create a ROS node and query from within this node. The queries have the same format as the ones used in the example above, the only difference is that they are not called with the rosprolog command line but with python code.
Check the example_node.py in the knowrob_intro package. Note that this node uses the PrologQuery class to make querying easier.
To run this example, first start knowrob_intro as we did before:
cd ~/knowrob_intro
docker-compose up
And in another terminal, instead of rosprolog, run:
docker run -it --rm --net knowrob_network --env ROS_MASTER_URI=http://knowrob:11311 knowrob_intro rosrun knowrob_intro example_node.py
After running the second docker container with the example_node, a lot of stuff should have been printed. Let's dive into the queries that generated this output.
What are the instances of Milk?
The following query returns all instances of the Milk class
# (Find) all instances of milk
query = pq.prolog_query("instance_of(X, pap:'Milk').")
print("Instances of Milk:")
pq.get_all_solutions(query)
The correspondent result should be something like this:
Instances of Milk:
X : milk1
It means that there is only one instance of Milk, and that this instance is milk1
What are the instances of Food?
The following query returns all instances of the Food class
# (Find) all instances of Food
query = pq.prolog_query("instance_of(X, pap:'Food').")
print("Query result:")
print("Instances of Food:")
food_instances = pq.get_all_solutions(query)
print("First food instance found {}".format(food_instances[0]))
The correspondent result should be:
Instances of Food:
X : hagelslag1
X : honey1
X : milk1
X : rice1
X : yogurt1
It means that there are 5 instances of Food
What is the weight of a certain instance of Milk?
The following query returns the mass value of milk1
query = pq.prolog_query("holds(pap:'milk1', soma:hasMassAttribute, X)")
print("Mass attribute of milk1:")
pq.get_all_solutions(query)
query = pq.prolog_query("holds(pap:weight_value_milk, soma:hasMassValue, X)")
print("Value of mass attribute of milk1:")
pq.get_all_solutions(query)
The correspondent result should be:
Mass attribute of milk1:
X : weight_value_milk
Value of mass attribute of milk1:
X : 0.7
This means that milk1 weights 0.7
Note: Differently from the example with rosprolog, we used the hold predicate instead of triple, but triple should also work in this case.

Query with reasoning and custom predicates

So far, we covered how to query the knowledge base using general KnowRob predicates, such as triple or instance_of. With these queries, you can retrieve knowledge that is explicitly modeled/included in the knowledge base. However, one of the most interesting aspects of KnowRob is its ability to infer new facts that are not explicitly modeled through reasoning. This is achieved with custom Prolog rules.
Let’s use the knowrob_intro package again as an example, check the owl_kb_prolog_rules.pl Prolog module.
In the first part of the file, the custom predicates that this module implements are declared. Thus, when Knowrob loads this module, these predicates will be available for querying, e.g., is_perishable('milk1'). Note that /1 indicates the arity of the predicate, i.e., the number of arguments it takes.
% module definition and public (externally visible) predicates
:- module(owl_kb_prolog_rules,
    [
        is_perishable/1,
        is_nonperishable/1,
        location/2,
        transfer/3
    ]).
After declaring the available predicates, it is necessary to define what they do. In the following example, is_perishable(X) is equal to instance_of(X, pap:'Perishable'). , i.e., it is basically used to shorten the syntax. Similar for is_nonperishable(X) and location(X, L).
is_perishable(X) :- instance_of(X, pap:'Perishable').
is_nonperishable(X) :- instance_of(X, pap:'NonPerishable').
location(X,L) :- triple(X, dul:'hasLocation', L).
The following example is a bit more involved, transfer finds a refrigerated table (L2) where the perishable items (X) that are in a non refrigerated table (L1) can be placed, and the opposite for non-perishable items.
% returns if the transfer operation is correct accordng to the rule that
% perishable items should be in a refrigerated table
transfer(X, L1, L2) :- is_perishable(X),
                        location(X,L1),
                        instance_of(L1, pap:'NonRefrigeratedTable'),
                        instance_of(L2, pap:'RefrigeratedTable').

transfer(X, L1, L2) :- is_nonperishable(X),
                    location(X,L1),
                    instance_of(L1, pap:'RefrigeratedTable'),
                    instance_of(L2, pap:'NonRefrigeratedTable').
 
Let’s run this example. Edit the docker-compose.yml file by replacing command: roslaunch knowrob knowrob.launch with command: roslaunch knowrob_intro example_owl_kb_and_prolog_rules.launch. Then, run docker compose:
cd ~/knowrob_intro
docker-compose up
The output should be something like:
knowrob_container | D : table_1
knowrob_container | O : table
knowrob_container | X : milk1
knowrob_container | D : table_1
knowrob_container | O : table
knowrob_container | X : yogurt1
knowrob_container | D : table
knowrob_container | O : table_1
knowrob_container | X : hagelslag1
knowrob_container | D : table_0
knowrob_container | O : table_1
knowrob_container | X : hagelslag1
knowrob_container | D : table
knowrob_container | O : table_1
knowrob_container | X : honey1
knowrob_container | D : table_0
knowrob_container | O : table_1
knowrob_container | X : honey1
This means that milk1 can be moved from table to table1. Note that this knowledge about transfers was not explicitly included in the knowledge base, it was inferred using the transfer rule.
You can find more examples of custom predicates in the sc_planning.pl module.

Custom KnowRob package

You can find information on how to create your own KnowRob package in KnowRob’s official documentation. But let’s recap here anyway, using the knowrob_intro package again as an example. To create your custom package you need to:
  1. Create a new ROS package for your project
  1. Include an __init__.pl file in the src folder, registering the package and including all custom prolog modules you define
  1. Include your custom prolog modules in the src folder, with all the custom predicates you want to use
  1. Add launch files pointing rosprolog to your package
  1. If you want to run it with docker, add a dockerfile and docker-compose.yml with your stuff
 
That is it!

References and relevant links

 
To achieve its task, the AUV is capable of performing