HTTPS SSH

SARL Capacity for Prolog Knowledge Bases

This project provides a Prolog knowledge-base capacity for SARL agents and TWO skills for it using SWI Prolog. The Prolog capacity/skill can be used as belief representation for agents, as a replacement of Java data-structures. This yield much more succinct and declarative agent systems.

The capacity provided is KB_Prolog (under package io.sarl.extras). The skills are SWI_KB_Prolog and SWIJPL_KB_Prolog.

The skills implementing the capacity rely on Mochalog and JPL frameworks, which provide a high-level view and API of SWI Prolog in Java.

This package can be obtained via Maven using JitPack: https://jitpack.io/#org.bitbucket.ssardina-research/sarl-prolog-cap

Version convention: Major.Minor.<SARL Version>. For example, 1.3.0.7.2 is version 1.3 for SARL 0.7.2.


TABLE OF CONTENTS


PRE-REQUISITES

The capacity and skills depend on the following two systems/frameworks:

Also, depending on the system being used:

  • If in Windows:
    • Tested successfully in Windows 7 with SWI 7.6.4.
    • Make sure SWI is installed with the JPL Java-SWI connectivity. You should have a jpl.dll (in the SWI bin/ subdir) and a jpl.jar (in the SWI lib/ subdir).
    • Define a system environment variable SWI_HOME_DIR and set it to the root directory of your installed version of SWI-Prolog (e.g., to C:\Program Files\swipl).
    • Extend Path system environment variable with the following two components:
      • %SWI_HOME_DIR%\bin
      • %SWI_HOME_DIR%\lib\jpl.jar
    • No changes to CLASSPATH are needed.
  • If in Linux:
    • Latest package versions at http://www.swi-prolog.org/build/PPA.txt
    • JPL is provided via package swi-prolog-java (interface between Java and SWI) installed. This will include library libjpl.so (e.g., /usr/lib/swi-prolog/lib/amd64/libjpl.so)
    • Extend environment library LD_PRELOAD for system to pre-load libswipl.so: export LD_PRELOAD=libswipl.so:$LD_PRELOAD
    • Extend environment variable LD_LIBRARY_PATH to point to the directory where libjpl.so is located (e.g., export LD_LIBRARY_PATH=/usr/lib/swi-prolog/lib/amd64/)
    • If using RUN AS configuration in ECLIPSE, remember to set up these two variables LD_LIBRARY_PATH and LD_PRELOAD too (and check "Append environment to native environment").

Develop it further

To develop this capacity/skills framework further, one would need:

  • Java Runtime Environment (JRE) and Java Compiler (javac) v1.8 (Sun version recommended)
  • Maven project management and comprehension tool (to meet dependencies, compile, package, run).
  • SARL modules and execution engine

To verify you have everything setup well, run mvn clean package first. This will run some unit testing on JPL itself (file src/test/java/io/sarl/extras/JPLTest.java. Furthermore, you can then run the SARL test agents:

* `io.sarl.extras.TestAgt_SWI`: dummy agent testing Mochalog-based skill **SWI_KB_Prolog**. Check [source here](src/main/sarl/io/sarl/extras/TestAgt_SWIJPL.sarl).
* `io.sarl.extras.TestAgt_SWIJPL`: dummy agent testing JPL-based skill **SWI_KB_Prolog**. Check [source here](src/main/sarl/io/sarl/extras/TestAgt_SWI.sarl).

Both test agents are registered in the BootTestAgt class, which if run with no arguments will ask which agent test to execute. You can run that booting class by doing: mvn -o exec:java. Both tests will at the end dump the Prolog databases into directory my_dump_test.

Check the source of the above two test agents to see the types of queries, from simple to more complex, that one could do.


Include capacity/skills in your SARL application via Maven

To add the dependency to this capacity/skills in your SARL application, you can use Maven with JitPack, by adding this dependency and repository in to your pom.xml:

    <!--  SARL PROLOG CAPACITY -->
    <dependency>
        <groupId>org.bitbucket.ssardina-research</groupId>
        <artifactId>sarl-prolog-cap</artifactId>
        <version>-SNAPSHOT</version>
    </dependency>

    <!-- JitPack used for remote installation of dependencies from Github and Bitbucket -->
    <repository>
        <id>jitpack.io</id>
        <name>JitPack Repository</name>
        <url>https://jitpack.io</url>
    </repository>

The JitPack link for this repository is here](https://jitpack.io/#org.bitbucket.ssardina-research/sarl-prolog-cap). Replace -SNAPSHOT by the specific version (e.g., commit id) you want to use in your application.


WHAT IS PROVIDED:

CAPACITY KB_PROLOG

This KB_Prolog capacity provides the following hooks to Prolog access:

  • KB system tools:
    • consult_file(file : String): consult file into Prolog engine.
    • dump_kb(): dump the current knowledgebase to a file with timestamp and registered name for kb.
    • dump_kb(id : String): : dump the current knowledgebase to a file with timestamp and name of kb.
    • get_prolog_engine() : Object: gives the prolog reference.
    • get_kb_name() : String: gives the registered name of the kb.
    • set_dump_root(dir : String): set the root directory where to dump KBs.
  • Assert and retract predicates:
    • assertFirst(queryS : String, params : Object*)
    • assertLast(queryS : String, params : Object*)
    • retract(queryS : String, params : Object*)
    • retractAll(queryS : String, params : Object*)
  • Queries:
    • prove(queryS : String, params : Object*) : boolean: prove if a queryS is true
    • askOnce(queryS : String, params : Object*) : Map<String, Term>: ask a query and get first result.
    • askForAllSolutions(QueryS : String, params : Object*) : Collection<Map<String, Term>>: return the set of all solutions as set of bindings.
    • ask(queryS : String, params : Object*) : Iterator: returns an iterator to solutions.
    • ask2(queryS : String, params : Object*) : Iterator: returns an iterator to solution bindings Map<String,Term>.

SKILL SWIJPL_KB_Prolog (via JPL) [RECOMENDED]

This skill is the recommended one to use and basically relies directly on the JPL infrastructure. The main tools at disposal to extend this skill are JPL:

  • Query.hasSolution: boolean result answering whether the query is true or not.
  • Query.oneSolution: a Map<String,Term> returning a binding if there is a solution to the query, otherwise null.
  • Query.allSolution: an array Map<String,Term>[] returning a set of bindings for all solutions (length zero if no solutions available).
  • Query.allSolution: an array Map<String,Term>[] returning a set of bindings for all solutions (length zero if no solutions available).
  • Query.hasNext(): boolean stating whether there is a "next" solution available for the query. Will re-start the query if it is executed again after being false.
  • Query.next(): returns the next solution, in the form of a Map<String,Term>, if there is one. Exception if we have already arrived to the last one.

So what does the skill provide beyond JPL itself? In a nutshell, two things:

  1. Automatic handling of local agent KB, so that each agent can keep its own KB. This is done by using SWI modules, because the Prolog engine itself is the same for everyone.
  2. A higher abstraction in queries when using placeholders ?. To fill the placeholders, we do not need to create specific JPL terms (such as JPL Atom, Integer, Float, Compound, JRef, or Variable), but we can just write the content and the skill will figure out its type:
    • If it is a string starting with a capital letter, then it is a variable term, e.g., X or Numero.
    • If it is a number without decimals, it is an integer term, e.g., 23 or 123.
    • If it is a number with decimals, it is a float term, e.g., 23.21.
    • If it is quoted with, then it is an atom, e.g., this is a complex(123) atom.
    • If nothing above applies, and has no (, [ or is, then it is also an atom, e.g., hello or sebastian.
    • If itself is a JRef object, then it is indeed already a JRef term.
    • Otherwise it is a compound term, like [1,2,3,4], father(maria, john), or X is Y + 23.

To state placeholders, use the ? symbol and a string, number or JRef filler, such as:

    assertFirst("agentName(?)", mySARLname) // myName is a string

    val solution = askOnce("get_player_last_loc(?, ?, Lat, Long)", playerName, 23)
    if (solution !== null) {
            agent_says(
                "Player **{0}** location is ({1},{2}) and charge is {3} at step {4}", 
                playerName,
                solution.get("Lat").floatValue,
                solution.get("Long").floatValue,
                solution.get("Charge").floatValue,
                solution.get("Step").intValue
                )
        } else {

In this skill one can pass Prolog a Java object, and SWI will be able to use it (e.g., call a method on it). For example:

    // JREF
    val int_obj : Integer = new Integer(232)
    solution = askOnce("print_integer(?, ?)", JPL.newJRef(int_obj), "N")
    if(solution === null) return false
    info("Solution for N: {0}", solution.get("N").intValue)

while the Prolog counterpart is:

    %% Check what can you do from Prolog to call Java: http://www.swi-prolog.org/pldoc/man?section=jpl
    print_integer(JRef, X2) :-
    %    jpl_get(JRef, intValue, X),         % this if it is accessing a field
        jpl_call(JRef, intValue, [], X),    % X should be the int value of object Integer JRef
        jpl_ref_to_type(JRef, T),           % T should be class([java,lang],[Integer])
        jpl_type_to_classname(T, ClassName),    % ClassName should be java.lang.Integer
        X2 is X+1,
        format(string(Text), "MESSAGE FROM PROLOG: The integer value of JAVA object (~s) is ~d", [ClassName, X2]),
        writeln(Text).

To see what you can do from Prolog in terms of Java objects, refer to the Prolog API section in the JPL home page.


SKILL SWI_KB_Prolog (via Mochalog)

The SWI_KB_Prolog is built on top of the Mochalog framework for more high-level access to SWI Prolog via the JPL interface. The implementation of the above primitives is, basically, by using the Mochalog API. In turn, Mochalog relies on JPL](https://jpl7.org/) framework.

Some useful notes:

  • Refer to the Mochalog readme to understand how to build queries using Mochalog (e.g., using @ placeholders).
  • Mochalog API does not allow passing Java objects (JRef) to Prolog (the JPL-based skill does!).
  • To handle responses from Prolog, where variables are grounded to Terms, we use the Term class form JPL
  • To handle pairs of variable name and term unified to, use Pair class; see example below.
  • The function ask(queryS : String, params : Object*) : Iterator: returns a Mochalog's QuerySolutions iterator. You can get the actual variable to term mappings via method .bindings
  • The params arguments above refer to SARL variadic arguments (in Java, called Varargs) to help build the query string by "filling" places using @A, @I, and @S placeholders. For example:
assertFirst("percepts(@A, @I, @S)", agentName, agents.get(agentName).step, percepts.toString)

An alternative, Java-based way to construct the query string, is to use String.format:

assertFirst(String.format("percepts(\'%s\', %d, %s)", agentName, agents.get(agentName).step, percepts.toString))

See how single-quoted was used here to make sure agentName becomes an atom (and not a string!).


USING SWI-Prolog IN SARL AGENTS/APPLICATIONS

There are basically three ways one can use SWI-Prolog inside SARL agents, depending on the level of abstraction:

  1. [RECOMMENDED] Create a capacity KB_Domain for your domain application that embodies the usual KB queries required, and a skill SWI_KB_Domain for it extending skill SWI_KB_Prolog (which implements general Prolog capacity KB_Prolog) in the SARL Prolog Capacity framework that implements those queries. The SARL agent will use this capacity and skill.
  2. Make the agents directly use capacity KB_PROLOG (and its default skill SWI_KB_Prolog). As soon as the agent acquires such a skill, a Prolog engine will be created by the skill. Then the agent for example can load a KB by consulting the file: consult_file('myKB.pl'). This is similar to the first option but the code will be in the SARL agent itself rather than encapsulated in a domain capacity/skill.
  3. Make the agents directly access SWI-Prolog via the Mochalog or JPL APIs (depending which skill you use), for example, by creating a Prolog engine in the initialization of agents, etc.

1 - Creating a domain-specific Knowledge-base capacity/skill.

This is the recommended approach. The idea is to create a capacity KB_Domain for your domain application that embodies the usual KB queries required, and a corresponding skill SWI_KB_Domain for it that extends the base SWI_KB_Prolog skill (which itself implements general Prolog capacity KB_Prolog) in the SARL Prolog Capacity framework that implements those queries.

The domain-dependent SWI_KB_Domain skill will have access to all the SWI Prolog tools provided in skill SWI_KB_Prolog and can implement the domain queries via SWI queries using the Mochalog and JPL infrastructures.

Under this approach, the SARL agent will: * Use capacity KB_Domain, which is the capacity for the queries of the domain. * A SARL agent will only use the queries provided by this capability via its functions. * Use skill SWI_KB_Domain, which implements capacity KB_Domain and extends SWI_KB_Prolog. * It is this skill that will perform Prolog queries via the Prolog tools offered by KB_Prolog.

Note that the functions in SWI_KB_Prolog will NOT be visible to the SARL agent itself, who can only access functions defined in domain capacity KB_Domain. If the SARL agent wants to do direct Prolog queries, it can also use capacity KB_Prolog, which means that the SWI-based functions implemented in skill SWI_KB_Prolog are now accessible at the agent level.

Here are the steps to this approach:

  1. Create a Capacity C for your application that provides the main queries to your domain.
    • For example, KB_Elevator capacity for an elevator domain with functions such as:
      • kb_load(file : String): load the KB encoded in a file.
      • kb_registerCarRequest(floor : int, dir : String): register that there has been a request in a floor towards a direction.
      • kb_getNextJob() : Pair<Integer, Direction>: get the next job (floor & direction) to serve. (see the Pair class)
    • Observe this capacity can be implemented in many ways, for example, with plan Java.
  2. Create a skill S for the capacity C that will be a Prolog Knowledgebase.

    • The skill will extend a skill for KB_Prolog, for example it can extend the skill SWIJPL_KB_Prolog. This means that everything in the KB_Prolog capacity will be available in S so that S can use Prolog to implement the domain queries. For example:

          skill SWI_KB_Elevator extends SWIJPL_KB_Prolog implements KB_Elevator  {
      
              val logging_level : int
              new (l : int = 0, name : String) {
                  super(name) // Call the super's constructor
                  logging_level = l
              }
      
              def kb_registerCarRequest(floor : int, dir : String) {
                  assertFirst("open_car_request(?, ?)", floor, dir)
              }
      
              def kb_load(file : String) {
                  consult_file(file)
              }
      
              ....
          }
      
  3. Your application will use skill S (for domain capacity C):

            import au.edu.rmit.agtgrp.elevatorsim.sarlctrl.beliefs.KB_Elevator
            import au.edu.rmit.agtgrp.elevatorsim.sarlctrl.beliefs.SWI_KB_Elevator
    
            // http://gangmax.me/blog/2017/10/10/how-to-return-multiple-values-from-a-java-method/
            import org.apache.commons.lang3.tuple.Pair
    
            setSkill(new SWI_KB_Elevator(0, "agent23", "my_dump"))
    
            // Load agent knowledge base
            kb_load("src/main/prolog/sweeper_elevator_agent.pl")
            reportMessage("I have loaded the SWI KB successfully!")
    
            on CarRequestPercept 
            {
                reportPersonRequestedService(occurrence.floor, occurrence.direction)
    
                // Add car request to our beliefs
                kb_registerCarRequest(occurrence.floor, occurrence.direction.name);
    
                // This action comes (is inherited) from KB_Prolog directly
                kb_dump()
            }
    
            /**
             * Handle the most preferable next request as soon as
             * it is available. Preference is defined by our beliefs.
             */
            private def performNextJob
            {
                // Begin polling for new jobs asynchronously
                execute [
                    val job : Pair<Integer, Direction> = kb_getNextJob()
                    val destination : int = job.left
                    val direction : Direction = job.right
    
                    reportTravellingTo(carID, destination, direction)
    
                    // Send car to destination by communicating it to Boss (and everyone else)
                    var sendCar = new SendCarAction(carID, destination, direction)
                    emit(sendCar) // Notify the SweeperBossAgent
                ]
            }
    

2 - Directly using the capacity and skill in agents.

Here, instead of creating a domain capacity and skill for the common Prolog accesses that the application will do (e.g., common queries), we can make the agents directly use the KB_Prolog capacity. This means that the behaviors of the SARL agent will make the Prolog access directly.

If you use SWIJPL_KB_Prolog Mochalog-based skill:

            setSkill(new SWI_KB_Prolog("agent23"))

            // Load agent knowledge base
            consult_file("src/main/prolog/sweeper_elevator_agent.pl")
            reportMessage("I have loaded the SWI KB successfully!")

            on CarRequestPercept 
            {
                reportPersonRequestedService(occurrence.floor, occurrence.direction)

                // Add car request to our beliefs
                assertFirst("open_car_request(?, ?)", occurrence.floor, occurrence.direction.name)

                // This action comes (is inherited) from KB_Prolog directly
                kb_dump()
            }

If you use SWI_KB_Prolog Mochalog-based skill:

            setSkill(new SWI_KB_Prolog("agent23"))

            // Load agent knowledge base
            consult_file("src/main/prolog/sweeper_elevator_agent.pl")
            reportMessage("I have loaded the SWI KB successfully!")

            on CarRequestPercept 
            {
                reportPersonRequestedService(occurrence.floor, occurrence.direction)

                // Add car request to our beliefs
                assertFirst("open_car_request(@I, @A)", occurrence.floor, occurrence.direction.name)

                // This action comes (is inherited) from KB_Prolog directly
                kb_dump()
            }

3 - SWI-Prolog Access via JPL or Mochalog

In this approach, we directly use SWI-Prolog via the JPL or Mochalog high-level infrastructure.

Here is some example code of JPL-based use (though for another application) in an elevator controller. Please observe the use of a module name to encapuslate the beliefset of the particular agent:

        import org.jpl7.Query

        // Set-up Prolog knowledgebase
        val beliefSpace = String.format("swiplayer")
        consult(System.format("%s:(%s)", beliefSpace, "src/main/prolog/masssim_coordinator.pl") // newest version

        // Assert percepts in the KB
        Query.hasSolution(System.format("%s:percepts(?, ?, ?)", beliefSpace), agentName, agents.get(agentName).step, percepts.toString)

        // Querying one solution - Tell the KB to process last percept
        agents.keySet().forEach([ agentName : String |
            Query.oneSolution("process_last_percepts(?)", agentName)
        ])

        // Querying all solutions - Report percepts available in the KB
        for (solution : allSolutions("percepts(Agent, Step, Percepts)"))
        {
            System.out.format("Information for agent %s on step %d\n", solution.get("Agent").toString(),  solution.get("Step").intValue)
        }

And here is some example code for the Mochalog-based ersion:

        import io.mochalog.bridge.prolog.PrologContext
        import io.mochalog.bridge.prolog.SandboxedPrologContext
        import io.mochalog.bridge.prolog.query.Query

        // Set-up Prolog knowledgebase
        var prolog_kb : PrologContext
        val beliefSpace = String.format("swiplayer")
        prolog_kb = new SandboxedPrologContext(beliefSpace)
        prolog_kb.importFile("src/main/prolog/masssim_coordinator.pl") // newest version

        // Assert percepts in the KB
        prolog_kb.assertFirst("percepts(@A, @I, @S)", agentName, agents.get(agentName).step, percepts.toString)

        // Querying one solution - Tell the KB to process last percept
        agents.keySet().forEach([ agentName : String |
            prolog_kb.askForSolution(Query.format("process_last_percepts(@A)", agentName))
        ])

        // Querying all solutions - Report percepts available in the KB
        val query = Query.format("percepts(Agent, Step, Percepts)")
        for (solution : prolog_kb.askForAllSolutions(query))
        {
            System.out.format("Information for agent %s on step %d\n", solution.get("Agent").toString(),  solution.get("Step").intValue)
        }

TROUBLESHOOTING

  • Did you get something like this?

        ERROR: /usr/lib/swi-prolog/library/process.pl:53:
            /usr/lib/swi-prolog/library/process.pl:53: Initialization goal raised exception:
            '$open_shared_object'/3: /usr/lib/swi-prolog/lib/amd64/process.so: undefined symbol: Sfilefunctions
        ERROR: /usr/lib/swi-prolog/library/prolog_pack.pl:52:
            Exported procedure process:process_kill/2 is not defined
        ERROR: /usr/lib/swi-prolog/library/prolog_pack.pl:52:
            Exported procedure process:process_group_kill/2 is not defined
        ERROR: /usr/lib/swi-prolog/library/prolog_pack.pl:52:
            Exported procedure process:process_wait/3 is not defined
        java: symbol lookup error: /usr/lib/swi-prolog/lib/amd64/readutil.so: undefined symbol: PL_new_atom
    

    Then you may not have set LD_PRELOAD env variable correctly.


CONTACT

  • Sebastian Sardina (ssardina@gmail.com)

LICENSE

This project is using the GPLv3 for open source licensing for information and the license visit GNU website (https://www.gnu.org/licenses/gpl-3.0.en.html).

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.