Skip to content

JavaInteractingWithTheKnowledgeBase

dskyle edited this page Jul 22, 2018 · 9 revisions

Java Guide Series
Architecture | Knowledge Base (Custom Types) | Networking | Containers | Threads


The MADARA Knowledge Base

The main entry point into the MADARA middleware is the Knowledge Base. The Knowledge Base supports a variety of programming styles and interactions, and we'll discuss many of those capabilities on this page.


Table of Contents


1. Knowledge Base Creation

There are two constructors for knowledge bases that are used most frequently by developers. The first is a default constructor which takes no arguments.

This constructor creates a knowledge base without a networking transport and is ideal for local reasoning using the Knowledge and Reasoning Language or for a knowledge base that is shared between threads for multi-threaded applications. Local variables and global variables with the default constructor will not be disseminated on a transport, but will be available to any thread with a handle to the knowledge base.

Example of default constructor

# import the KnowledgeBase class
import ai.madara.knowledge.KnowledgeBase;

# create a knowledge base with no transport
knowledge = engine.KnowledgeBase ();

# set our id to 0 and let other threads know that we are ready
knowledge.set(".id", 0);
knowledge.set("agent{.id}.ready", 1);

return 0;

The second major constructor is for creating a networked knowledge base using one of the supported transports (UDP, Multicast, Broadcast, Open Splice DDS, and RTI DDS). This constructor takes two parameters: 1) a unique identifier and a 2) QoSTransportSettings class.

If the unique identifier is an empty string (""), then a default unique identifier is created using the hostname set in the OS and an ephemeral port binding. The transport settings class contains dozens of configuration options for the transport layer. We'll discuss these options a bit more in the next wiki page.

Example of UDP transport constructor example

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.transport.QoSTransportSettings;
import ai.madara.transport.TransportType;

//Create transport settings for a multicast transport
QoSTransportSettings settings = new QoSTransportSettings();
settings.setHosts(new String[]{"localhost:40000", "localhost:40001});
settings.setType(TransportType.UDP_TRANSPORT);

//create a knowledge base with the multicast transport settings
KnowledgeBase knowledge = new KnowledgeBase("", settings);

//send all agents listening on this multicast address the knowledge
//that all agents are currently ready
knowledge.evaluate("all_agents_ready = 1");

Example of multicast transport constructor (Multicast IP range)

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.transport.QoSTransportSettings;
import ai.madara.transport.TransportType;

//Create transport settings for a multicast transport
QoSTransportSettings settings = new QoSTransportSettings ();
settings.setHosts(new String[]{"239.255.0.1:4150"});
settings.setType(TransportType.MULTICAST_TRANSPORT);

//create a knowledge base with the multicast transport settings
KnowledgeBase knowledge = new KnowledgeBase("agent1", settings);

//send all agents listening on this multicast address the knowledge
//that all agents are currently ready
knowledge.evaluate("all_agents_ready = 1");

Example of broadcast transport constructor

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.transport.QoSTransportSettings;
import ai.madara.transport.TransportType;

//Create transport settings for a multicast transport
QoSTransportSettings settings = new QoSTransportSettings ();
settings.setHosts(new String[]{"192.168.1.255:15000"});
settings.setType(TransportType.BROADCAST_TRANSPORT);

//create a knowledge base with the multicast transport settings
KnowledgeBase knowledge = new KnowledgeBase("", settings);

//send all agents listening on this multicast address the knowledge
//that all agents are currently ready
knowledge.evaluate("all_agents_ready = 1");
}

For more information on interacting with the Transport Settings class, see [JavaInteractingWithTheTransport].


2. Evaluate, Get, Set, and Wait


2.a. Get, Set, and Set_Index

The most basic manner to interact with the knowledge base is as a real-time dictionary. In the following example, we show how to do basic gets and sets and also how to aggregate updates for semantic consistency.

Example of Using Get and Set

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.knowledge.EvalSettings;
import ai.madara.transport.QoSTransportSettings;
import ai.madara.transport.TransportType;
import someGpsModule;

# Create transport settings for a multicast transport
QoSTransportSettings settings = new QoSTransportSettings ();
settings.setHosts(new String[]{"239.255.0.1:4150"});
settings.setType(TransportType.MULTICAST_TRANSPORT);

# Use default eval settings, which are different than the
# defaults that are passed to set
EvalSettings evalSettings = engine.EvalSettings();

# use the transport constructor 
KnowledgeBase knowledge = new KnowledgeBase("", settings);

#set .id to a command line argument (not shown here)

# set the global variable agent{.id}.ready
knowledge.set("agent{.id}.ready", 1, evalSettings);

# delay sending modifications for next few set commands
evalSettings.setDelaySendingModifieds(true);

# use some third party library to get our current GPS position
# this is NOT a MADARA function and is used as an example of working
# with arbitrary libraries or code bases
gpsCoords = someGpsModule.getCurrentGps();

# now use these gpsCoords to update other agents on your position
# we've set the eval settings to delay sending modifications until
# we ask it to no longer delay during a set, eval, or wait command
# or if we explicitly call send_modifieds

# here we create an array of 3 doubles called "agent{.id}. position". By setting
# the last index (2) first, we allocate the full array instead of
# growing it from 0 to 2, which would be much slower.
knowledge.setIndex("agent{.id}.position", 2, gpsCoords.getAltitude(), evalSettings);
knowledge.setIndex("agent{.id}.position", 0, gpsCoords.getLatitude(), evalSettings);
knowledge.setIndex("agent{.id}.position", 1, gpsCoords.getLongitude(), evalSettings);

# aggregate a timestamp in milliseconds with the update of the above position
knowledge.set("agent{.id}.position.timestamp", System.currentTimeMillis(), evalSettings);

# send all modifications in the same packet for consistency
knowledge.sendModifieds();

2.b. Evaluate and Wait

Developers are free to use get and set-based operations exclusively to minimize interaction with MADARA except for storing and transferring data between agents in a network. However, many features in MADARA are only available through the evaluate and wait calls.

Evaluate and Wait are mechanisms for evaluating Knowledge and Reasoning Language (KaRL) logic, in order to perform artificial intelligence operations, call user functions, and wait for new information to come in from the network. The main difference between evaluate and wait is that wait will block the calling application until a condition is true (non-zero).

Example showcasing the difference between evaluate and wait

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.knowledge.EvalSettings;
import ai.madara.transport.QoSTransportSettings;
import ai.madara.transport.TransportType;
import someGpsModule;

# Create transport settings for a multicast transport
QoSTransportSettings settings = new QoSTransportSettings ();
settings.setHosts(new String[]{"239.255.0.1:4150"});
settings.setType(TransportType.MULTICAST_TRANSPORT);

# Use default eval settings, which are different than the
# defaults that are passed to set
EvalSettings evalSettings = engine.EvalSettings();

# use the transport constructor 
KnowledgeBase knowledge = new KnowledgeBase("", settings);

#set .id to a command line argument (not shown here)

# assuming .id 0, then will set agent0.ready to 1
knowledge.evaluate("agent{.id}.ready = 1");

# in this advanced wait statement, we create a local counter called
# .agents_ready and we increment this counter whenever we find an
# agent ready. We use the choose right operator ";>" to tell the
# wait statement to choose the right most predicate as its truth
# condition. In this case, that condition is ".agents_ready > 5"
knowledge.wait(
  ".agents_ready = 0 ;> "
  ".i [0->10) (agent{.i}.ready => ++.agents_ready) ;> "
  "agent{.id}.ready = 1 ;> "
  ".agents_ready > 5"  # right most is the conditional because of ;>
);

# Once we get to this point in the code, we have at least 6 agents
# online and ready to conduct AI operations. Now, we create a
# state machine that each agent will do.
while (knowledge.get ("terminated").toLong() == 0)
{
  knowledge.evaluate (
    # call functions that sense the environment and update our location
    "sense_environment ();"
    "update_location ();"

    # choose what to do next based on a variable called 'state'
    "state 'hover' => hover ()"
    "||"
    "state 'land' => land ()"
    "||"
    "state 'takeoff' => takeoff ()"
    "||"
    "state 'move' => move () ;"

    # if battery level is low, terminate
    "battery_level < 10 => terminated = 1"
  );
}

2.c. Evaluate and Wait Settings

A call to evaluate, get, and set can be controlled and augmented by modifying the EvalSettings. The class WaitSettings extends the Eval_Settings class by adding specific controls for the wait function.

The following example shows how to configure a wait statement with various settings available through EvalSettings and WaitSettings.

Example of Configuring Wait with the Wait Settings class

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.knowledge.EvalSettings;

waitSettings = new WaitSettings();

# pre_print and post_print are atomic operations that occur just
# before and after, respectively, an evaluate, set, get, or wait call
waitSettings.setPrePrintStatement("The value of count before eval is {count}.\n");
waitSettings.setPostPrintStatement("The value of count after eval is {count}.\n");

# this variable indicates that changes to global variables will
# not be disseminated over the network and will instead be treated
# as a local change
waitSettings.setTreatGlobalsAsLocals(true);
  
# poll for truth every 0.5 seconds
waitSettings.setPollFrequency(0.5);

# wait for truth for at most 10 seconds
waitSettings.setMaxWaitTime(10.0);

# increase a counter and wait on the value of finished
# this is possible because of the choose right operator (;>) which
# executes the left side, then the right side, and returns the value
# of the right side
knowledge.wait("++count ;> finished", waitSettings);

For all options on these settings classes, see the documentation on WaitSettings and EvalSettings.


3. Function Calls

Developers can create their own Python functions and call them from inside of an evaluate or wait call.

Example of calling a Java function

# import the submodules and give them shorter aliases
import ai.madara.knowledge.KnowledgeBase;
import ai.madara.knowledge.MadaraFunction;
import ai.madara.knowledge.KnowledgeRecord;
import ai.madara.knowledge.KnowledgeList;
import ai.madara.knowledge.Variables;
import someGpsModule;

public class MoveToGps extends MadaraFunction
{
  KnowledgeRecord execute(KnowledgeList args, Variables variables)
  {
    KnowledgeRecord result = new KnowledgeRecord();

    if (args.size() == 3)
    {
      # assuming the GPS_Library::move_to_gps function returns
      # some kind of integer, double, or string
      result = new KnowledgeRecord(someGpsModule.moveToGps(
        args.get(0).toString(),
        args.get(1).toString(),
        args.get(2).toString());
    }

    return result;
  }
}

# We don't need a networking transport for this example
KnowledgeBase knowledge = new KnowledgeBase();

# define a function call which is called "move_to_gps" inside
# of evaluate and wait functions and maps to a C++ function called
# move_to_gps
knowledge.defineFunction("move_to_gps", new MoveToGps());

# call the mapped "move_to_gps" function with a latitude,
# longitude, and altitude.
knowledge.evaluate("move_to_gps (35.336488, -87.539062, 2000.0)");

4. System Calls

System calls in MADARA are much like function calls, but system calls are slightly faster and mapped to internal MADARA functions that provide information about internal knowledge records, time, quality, printing, utility functions, and even the ability to evaluate other logics.

Example of various MADARA system calls

# import the submodules and give them shorter aliases
import ai.madara.knowledge as engine;
KnowledgeBase knowledge = new KnowledgeBase();

knowledge.evaluate (
  # get time in nanoseconds
  "cur_time = #get_time (); "

  # convert nanosecond time to a string.  Can also use #string
  "str_cur_time = #to_string (cur_time); "

  # convert a directory structure to the host directory format
  # on Unix OSes, nothing would change, but on Windows, this would
  # be changed from '/' to '\'
  "file_name = #to_host_dirs ('/file/dir/structure/file.jpg'); "

  # read the file referenced by filename
  "buffer = #read_file (file_name); "

  # save the size of the file we read into a variable called file_size
  "file_size = #size (buffer); "

  # print all system call documentation.
  "print_system_calls (); "

  # write our file buffer to a different file
  "#write_file (#to_host_dirs ('/images/file.jpg'), buffer); "

  # generate a uniform random number between 1 and 8 (inclusive)
  "some_random_int = #rand_int (1, 8); "

  # generate a uniform random double between 2 and 2000 (inclusive)
  "some_random_double = #rand_double (2.0, 2000.0); "
);

5. Checkpointing

MADARA provides powerful debugging options for saving and loading contexts with a file store. In this section, we describe how to save the full context or an incremental checkpoint, and also how to load state from these files.


5.a. Saving the Full Context

Saving everything in a KnowledgeBase to a file is done with the save_context function, as shown below. This will save both global and local variables.

Example of saving context

import ai.madara.knowledge.KnowledgeBase;

KnowledgeBase knowledge = new KnowledgeBase();
# make changes to the context or have changes come in over the network
...

# we recommend saving to a "kkb" file -- KaRL Knowledge Base to help
# with sorting/visualization in a directory. This command saves every
# local and global variable to the context file.
knowledge.saveContext("recent_context.kkb");

5.b. Saving a Checkpoint

In contrast to save_context, save_checkpoint only saves updates since the last save_context or save_checkpoint. This function is far more efficient than save_context, especially for a large KnowledgeBase.

Saving only global changes since last full save

Example of saving checkpoint after save context

import ai.madara.knowledge.KnowledgeBase;

KnowledgeBase knowledge = new KnowledgeBase();

# make changes to the context or have changes come in over the network
...

# Save everything to a file
knowledge.saveContext("recent_context.kb");

# make additional changes to the context
...

# Append everything since save_context to the end of the file.
knowledge.saveCheckpoint("recent_context.kb");

Important note about checkpoint: Checkpointing will only save global variable changes. In order to save local variable changes, you will need to do evaluates, sets, and waits with track_local_changes enabled in the Eval Settings.


5.c. Loading a Checkpoint

MADARA provides the function load_context to read a context from a file. The function provides optional parameters to allow for either resetting the context to the contents of the file or simply adding the file's contents into the context, in addition to or overwriting whatever exists in the context.

Loading Context

import ai.madara.knowledge.KnowledgeBase;

KnowledgeBase knowledge = new KnowledgeBase();

# Load context from a file. The second parameter is the decision of
# whether or not to load the Knowledge Base's id from the file. We
# say false to indicate we want to keep the default id generated by
# the Knowledge Base constructor, but if we had indicated true as the
# second parameter, we would use the id that the agent1_context.kkb had
# used. A third parameter is available which takes an Eval_Settings
# class. The default settings here treat all updates as local updates
# (i.e., changes will not be disseminated as new updates), but this is
# tunable if you want to specify your own Eval Settings class.
knowledge.loadContext("agent1_context.kkb", false);

More Information

The Java module is fully documented with standard java documentation. Please use the API at JavaDocs or tell your editor of choice to load source files from the Git repository.


Java Guide Series
Architecture | Knowledge Base | Networking | Containers | Threads