Skip to content

JavaKnowledgeContainers

James Edmondson edited this page Jul 7, 2018 · 4 revisions

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


MADARA Containers

The MADARA KaRL scripting environment is fast and efficient, but it can also be difficult to debug and work with for programmers more familiar with object-oriented programming in their host languages. To address the needs of object-oriented developers, MADARA also provides a set of containers which present facades into the Knowledge Base and mimic the look and feel of STL containers. These object-oriented containers are meant to be user-friendly and intuitive, but some guidance may be useful for users who do not know where they are and how they may be initialized.


Table of Contents


1. Types of Containers

In this section, we breakdown the types of containers. For a complete list of containers, see the files located inside of the com.madara.containers namespaceof your source distribution, or see the Library documentation for the Containers namespace.


1.1 Instance Variables

Instance variables are facades into single variable locations within the Knowledge Base and are the most efficient of the MADARA containers. Values are set with a set function, available through the container, and retrieved using the get method or with a toInteger (), toDouble (), or toString () function. MADARA currently supports the following instance variables:


1.1.1 Integer

Location: ai.madara.knowledge.containers.Integer
Documentation: Integer

Integer is a container that provides interfaces for setting and getting an integer value from the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.Integer;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
Integer myId = new Integer();
myId.setName(knowledge, ".id");

// set id within the knowledge base to 1
myId.set(1);

...

// print the value to stdout
System.out.println ("My id is " + myId.get());

// free the C++ resources
myId.free();
knowledge.free();

1.1.2 Double

Location: ai.madara.knowledge.containers.Double
Documentation: Double

Double is a container that provides interfaces for setting and getting a double value from the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.Double;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
Double funds = new Double();
funds.setName(knowledge, ".funds");

// set funds to 300.50
funds.set(300.50);

...

// print the value to stdout
System.out.println ("My funds available are " + funds.get());

// free the C++ resources
funds.free();
knowledge.free();

1.1.3 String

Location: ai.madara.knowledge.containers.String
Documentation: String

String is a container that provides interfaces for setting and getting a string value from the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.String;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
String name = new String();
name.setName (knowledge,".name");

// set name to "John Smith"
name.set("John Smith");

...

// read the value later

// print the value to stdout
System.out.println ("My name is " + name.get());

// free the C++ resources
name.free();
knowledge.free();

1.2 Vectors/Arrays

Array containers are facades into arrays of variable locations within the Knowledge Base, and provide O(1) access times to these elements. Unlike with instance variables, the name that is set for an array container is the prefix for variable locations instead of an actual variable name. MADARA currently supports the following vector/array containers:


1.2.1 Integer Vector/Array

Location: ai.madara.knowledge.containers.IntegerVector
Documentation: IntegerVector

IntegerVector is a container that provides interfaces for setting and getting an integer value from the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.IntegerVector;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
IntegerVector myArray myArray = new IntegerVector();
myArray.setName(knowledge, ".array", 10);

// set index 1 of the array to true (1)
myArray.set(1, 1);

// set index 5 of the array to 30
myArray.set(5, 30);

// set index 0 of the array to 17
myArray.set(0, 17);

...

// read the values of index 0, 1, and 5
System.out.println("myArray.get(0) is " + myArray.get(0));
System.out.println("myArray.get(1) is " + myArray.get(1));
System.out.println("myArray.get(5) is " + myArray.get(5));

// free the C++ resources
myArray.free();
knowledge.free();

1.2.2 Double Vector/Array

Location: ai.madara.knowledge.containers.DoubleVector
Documentation: DoubleVector

Double_Vector is a container that provides interfaces for setting and getting an array of double values within the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.DoubleVector;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
DoubleVector myArray myArray = new DoubleVector();
myArray.setName(knowledge, ".array", 10);

// set index 1 of the array to 6.5
myArray.set(1, 6.5);

// set index 5 of the array to 31.3
myArray.set(5, 31.3);

// set index 0 of the array to 15.2
myArray.set(0, 15.2);

...

// read the values of index 0, 1, and 5
System.out.println("myArray.get(0) is " + myArray.get(0));
System.out.println("myArray.get(1) is " + myArray.get(1));
System.out.println("myArray.get(5) is " + myArray.get(5));

// free the C++ resources
myArray.free();
knowledge.free();

1.2.3 String Vector/Array

Location: ai.madara.knowledge.containers.StringVector
Documentation: StringVector

StringVector, also known as String_Array, is a container that provides interfaces for setting and getting an array of string values within the Knowledge Base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.StringVector;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
StringVector myArray myArray = new StringVector();
myArray.setName(knowledge, ".array", 10);

// set the indices of the array to characters in Snow White
myArray.set(0, "Snow White");
myArray.set(1, "Doc");
myArray.set(2, "Dopey");
myArray.set(3, "Bashful");
myArray.set(4, "Grumpy");
myArray.set(5, "Sneezy");
myArray.set(6, "Sleepy");
myArray.set(7, "Happy");
myArray.set(8, "Huntsman");
myArray.set(9, "Queen");

...

// print out all elements of the array
for (unsigned int i 0; i < 10; ++i)
  System.out.println("array.get(" + i + ") is " + myArray.get(i));

// free the C++ resources
myArray.free();
knowledge.free();

1.3 Maps

Map containers are facades into string-keyed maps of variable locations within the Knowledge Base. Unlike with instance variables, the name that is set for a map container is the prefix for variable locations instead of an actual variable name, and access times are O(log m), where m is the number of items existing in the Map container.

Location: ai.madara.knowledge.containers.Map
Documentation: Map

The Map class is one of the most versatile container classes available in MADARA. This class is likely to prove invaluable to developers wanting to emulate classes within the knowledge base.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.Map;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a container that points inside it
KnowledgeBase knowledge = new KnowledgeBase();
Map map = new Map();
map.setName(knowledge, ".profile.1");

// create profile fields for a man named John Smith at profile.1
map.set("name", "John Smith");
map.set("age", 30);
map.set("funds", 300.50);
map.set("photo1", "/images/profile/1/headshot.jpg");

// print the profile
System.out.println("Name: " + map.get("name"));
System.out.println("Age: " + map.get("age"));
System.out.println("funds: " + map.get("funds"));
System.out.println("Photo: " + map.get("photo1"));

/**
 * in a persistent application, we would actually want to free() whatever is returned by
 * map.get(...). Otherwise, you'll have a small memory leak.
 **/

// free the C++ resources
map.free();
knowledge.free();

1.4 Flexible Map

Flexible Maps are abstractions intended to support multi-dimensional arrays and maps. These containers provide both integer-based index and string-based accesses. These functions return other Flexible Maps which can either be set to an arbitrary value (such as an integer, string, double, byte buffer, etc.) or subindexed with integers or strings.

Location: ai.madara.knowledge.containers.FlexMap
Documentation: Map

FlexMap replaces the old VectorN class which was less intuitive and useful. FlexMap is O(1) for subindexing (e.g., [1][2]) and only incurs an O(log n), with n being the number of keys in the knowledge base, lookup on the first access of an element with an accessor or mutator function (e.g. to_string() or setting the value explicitly). FlexMap essentially always assumes an index is not a variable lookup until you are setting or getting a value explicitly. The keys function is an O(n) operation that must look through all keys in the Knowledge_Base to find all versions with the prefix set in constructor or set_name function.

Though the subindexing is O(1), developers should keep in mind that each index is a concatenation of the container name with a delimiter (by default '.') and the index, which is essentially 2 concatenation operations.

FlexMap includes to*Container operations that can convert the FlexMap into any supported container for fast O(1) changes later to String, Integer, Double, etc.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.FlexMap;
import ai.madara.knowledge.containers.String;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a multi-dimensional array
KnowledgeBase knowledge = new KnowledgeBase();
FlexMap map = new FlexMap();
String cityName;

map.setName(knowledge, "cities");

// set location cities.1.2.3.4.name to "St. Louis"
map.get(1).get(2).get(3).get(4).get("name").set("St. Louis");

// set location cities.4.2.3.4.name to "San Francisco"
map.get(4).get(2).get(3).get(4).get("name").set("San Francisco");

// output the value at [1][2][3][4]
System.out.println ("First town is " + map.get(1).get(2).get(3).get(4).get("name").toString());

// output the value at [4][2][3][4]
System.out.println ("Second town is " + map.get(4).get(2).get(3).get(4).get("name").toString());

// FlexMap can create containers to its elements for faster access
cityName = map.get(4).get(2).get(3).get(4).get("name").toStringContainer ();
cityName.set("Ontario");

System.out.println ("Second town is " + cityName.toString());

The above example shows the flexibility of using the FlexMap, but in reality, you will want to call free() on each return value of get() on a FlexMap. We do provide finalize overrides with each container ported to Java, but Java does not guarantee that such methods will ever be called. To make sure you free the underlying C++ memory, it is best practice to call free() explicitly when you are done using a FlexMap.


1.5 Counter

Counters are abstractions intended to support aggregate counters across multiple participating agents. Counters provide a solution to the problem of the many writers problem for single variable increments/decrements. What a Counter does under the hood is give each agent a copy of the variable, and the agent only increments/decrements/sets their versions of the variable. When another agent wants to know, the aggregate count, they call toInteger() or toRecord().

Location: ai.madara.knowledge.containers.Counter
Documentation: Map

Updating the Counter takes O(1) time. Retrieving a value from the Counter requires O(N) time, where N is the number of agents participating in the counting operation.

// import containers and KnowledgeBase
import ai.madara.knowledge.containers.FlexMap;
import ai.madara.knowledge.containers.String;
import ai.madara.knowledge.KnowledgeBase;

// create a knowledge base and make a multi-dimensional array
KnowledgeBase knowledge = new KnowledgeBase();
Counter entries = new Counter();

entries.setName(knowledge, "entries");

// resize our entries counter to have 2 variables (2 agents),
// with our id as 0 (the other agent would be 1)
entries.resize(0, 2);


// add 3 to the number of entries that you are maintaining
entries.inc();
entries.inc(2);

// other agents across the network may have updated their entry in "entries"
System.out.println ("Total entries in network is : " + entries.toString());

2. Interaction with the Transport Layer

When creating containers, each constructor supports a Madara::Knowledge_Engine::Knowledge_Update_Settings instance that provides control over the updating and retrieving of variables from the context. However, no function in the container classes directly interacts with the transport layer.

Container classes are intended to provide fast, efficient, and convenient facades into the Knowledge Base to support object-oriented programming. In order to send these updates to other reasoning agents in the network, functions will need to be called on the Knowledge_Base. In this section of the Wiki, we'll discuss how the containers are intended to be used and when interactions over the network occur.


2.1 The sendModifieds function

If the developer does not intend to use the evaluate or wait function on the Knowledge Base and intends to explicitly use MADARA containers, then the main option for communicating changes over the network is with the send_modifieds function. After all relevant changes have been made through the container classes, simply call send_modifieds.

To showcase this concept, we'll use a complete program that sends a profile over multicast.

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

//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);

// lock the context from any external updates until we're done
knowledge.lock();

Map map = new Map();
map.setName(knowledge, ".profile.1");

// create profile fields for a man named John Smith at profile.1
map.set("name", "John Smith");
map.set("age", Integer (30));
map.set("funds", 300.50);
map.set("photo1", "/images/profile/1/headshot.jpg");

// unlock the context to allow the network to update our context again
knowledge.unlock();

/**
 * send the changes we've made to profile.1 over the network
 * These will show up as updates to profile.1.name, profile.1.age,
 * profile.1.funds, and profile.1.photo1
 */ 
knowledge.sendModifieds();

// free the C++ resources
map.free();
knowledge.free();
settings.free();

2.2 Pairing containers with function calls from evaluate or wait

The more recommended way to use containers is within functions called from an evaluate or wait statement. There are several reasons for doing this:

  • Functions called from an evaluate are atomic and the context is already locked from outside updates
  • An evaluate call, by default, sends all modified variables over the attached network transports
  • MADARA containers allow for attaching a context through either the Variables or KnowledgeBase classes, so there is nothing gained by using containers outside of function calls from evaluate

In the next example, we create a function called setProfile that is called from an evaluate statement within the main function.

import ai.madara.knowledge.MadaraFunction;
import ai.madara.knowledge.KnowledgeRecord;
import ai.madara.knowledge.KnowledgeList;
import ai.madara.knowledge.Variables;
import someGpsModule;

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

    // create a map with a prefix that points into the context
    Map profile = new Map();
    profile.setName(variables, "profile.1");
    
    // set profile information for John Smith
    profile.set("name", "John Smith");
    profile.set("age", Integer (30));
    profile.set("funds", 300.50);
    profile.set("photo1", "/images/profile/1/headshot.jpg");  

    profile.free();
    
    return result;
  }
}

// from a main function

//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("", settings);

// define the setProfile function within the knowledge base
knowledge.define_function("setProfile", new SetProfile());

// call the set_profile function and immediately send updates
knowledge.evaluate("setProfile()");

// sleep for a few seconds
knowledge.evaluate("#sleep(3)");

// free the C++ resources
knowledge.free();
settings.free();                                                                             

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