NAV Navbar

Library Overview

The Bonsai libraries known as libbonsai (C++) and bonsai_ai (Python) wrap the Bonsai API to simplify the process of building simulators in C++ and Python programming languages. The C++ library is current in beta and not 100% on feature parity with the Python library. This library is compatible with Windows 7/10, macOS, and Linux.

When the AI Engine trains with the simulator it works in a loop. First, the simulator connects and registers itself with the AI Engine. Then, the simulator sends the AI Engine a state and the value of any objectives or rewards; next, the AI Engine replies with an action. The simulator then uses this action to advance the simulation and compute a new state. This “send state, receive action” process is repeated until training stops. At any time the AI engine may stop, reconfigure, and reset the simulator. After doing so it will either restart this training loop or stop training. A single state, action, next state, loop is sometimes referred to as an “iteration”.

The Config object has a built in command line parser which can be used to override the local running environment. It comes with a set of standard switches which can be viewed with the --help option. The current default configuration is read from environment variables, a ~/.bonsai configuration file in the user’s home directory, a ./.bonsai and a ./.brains configuration file in the current working directory, command line options, and programmatically in code.

Brain Class

Manages a BRAIN instance, talks with the server backend, and contains information about the BRAIN state. In future versions will be used to upload and download Inkling to and from the BRAIN on the server.

Requires a configuration and a BRAIN name. The BRAIN name can be set in several places, and there is an order of what takes precedence over the other as follows:

Brain() >> –brain >> .brains >> .bonsai[profile] >> .bonsai[DEFAULT] >> env[BONSAI_TRAIN_BRAIN]

such that “>>” indicates a decreasing order of precedence. Note that failure to set BRAIN name in at least one of these places will result in a friendly error.

Brain(config, name)

auto config = make_shared<bonsai::Config>(argc, argv);
auto brain = make_shared<bonsai::Brain>(config);
std::cout << brain << std::endl;

Creates a local object for interacting with an existing BRAIN on the server.

Argument Description
config Object returned by previously created Bonsai::Config.
name BRAIN name as specified on the server. If name is empty, the BRAIN name in config is used instead.

update()

brain.update();

Refreshes description, status, and other information with the current state of the BRAIN on the server. Called by default when constructing a new Brain object.

string name()

std::cout << brain.name() << endl;

Returns the name of the BRAIN as specified when it was created.

string description()

std::cout << brain.description() << endl;

Returns the user-provided description for the BRAIN.

int version()

std::cout << brain.version() << endl;

Returns the current version number of the BRAIN.

int latest_version()

std::cout << brain.latest_version() << endl;

Returns latest version number of the BRAIN.

Config config()

std::cout << brain.config() << endl;

Returns the configuration used to locate this BRAIN.

operator<<(ostream, brain)

Prints out a representation of Brain that is useful for debugging.

Argument Description
ostream A std C++ stream operator.
brain A bonsai::Brain object to print out.

Config Class

Example ~/.bonsai file

[DEFAULT]
username = admin
accesskey = None
profile = dev

[dev]
url = http://localhost:5000
username = admin
accesskey = 00000000-1111-2222-3333-000000000001

Manages Bonsai configuration environments. Config files can be specified in the user home directory, ~/.bonsai, or in a local directory. Configuration parameters can also be parsed from the command line.

The profile key can be used to switch between different profiles in one configuration file.

Config(profile)

#include <bonsai.hpp>
int main(int argc, char** argv) {
    auto config = bonsai::Config();
    std::cout << config << std::endl;
}

Constructs a default configuration.

Argument Description
profile Optional .bonsai profile name. Will use the DEFAULT profile if not specified.

Configurations are stored in ~/.bonsai and ./.bonsai configuration files. The local configuration file overrides settings in the configuration file in the user home directory.

Config(argc, argv, profile)

#include <bonsai.hpp>
int main(int argc, char** argv) {
    auto config = bonsai::Config(argc, argv);
    std::cout << config << std::endl;
}

Constructs a config by looking in the configuration files and parsing the command line arguments. To see the list of command line arguments, pass in the --help flag.

Argument Description
argc As passed to main(). (C++)
argv As passed to main(). (C++/Python)
profile Optional .bonsai profile name. Will use the DEFAULT profile if not specified.

Unrecognized arguments will be ignored.

accesskey()

my_config.accesskey() == "00000000-1111-2222-3333-000000000001";
my_config.set_accesskey("00000000-1111-2222-3333-000000000001");

Server authentication token. Obtained from the bonsai server. You need to set it in your config.

username()

my_config.username() == "alice";
my_config.set_username("alice");

Account user name. The account you signed up with.

url()

my_config.url() == "https://api.bons.ai";
my_config.set_url("https://api.bons.ai");

Server URL. Address and port number of the bonsai server. Normally you should not need to change this.

proxy()

my_config.proxy() == "myproxy:5000";
my_config.set_proxy("myproxy:5000");

Proxy Server. Address and port number of the proxy server to connect through.

brain()

my_config.brain() == "scarecrow";
my_config.set_brain("scarecrow");

BRAIN name. Name of the BRAIN on the server.

predict()

my_config.predict() == true;
my_config.set_predict(true);

Simulator mode. The mode in which simulators will run. True if running for prediction, false for training.

brain_version()

my_config.brain_version() == 0;
my_config.set_brain_version(0);

BRAIN version. The version of the brain to use when running for prediction. Set to 0 to use latest version.

record_file()

my_config.record_file() == "foobar.json";
my_config.set_record_file("foobar.json");

This property defines the destination for log recording. Additionally, the format for log recording is inferred from the file extension. Currently supported options are json and csv. Missing file extension or use of an unsupported extension will result in runtime errors.

record_enabled()

my_config.record_enabled() == true;
my_config.set_record_enabled(true);

record_format()

my_config.record_file() == "foobar.json";
my_config.record_format() == "json";

Note: This property cannot be set directly. It reflects the file extension of the currently configured record_file. json or csv are valid.

operator<<(ostream, config)

Will print out a representation of Config that is useful for debugging.

Argument Description
ostream A std c++ stream operator.
config A bonsai::Config object to print.

Simulator Class

Simulator state image

This class is used to interface with the server while training or running predictions against a BRAIN. It is an abstract base class, and to use it a developer must create a subclass.

The Simulator class is closely related to the Inkling file that is associated with the BRAIN. The name used to construct Simulator must match the name of the simulator in the Inkling file.

There are two main methods that you must override, episode_start and simulate. The diagram demonstrates how these are called during training. Optionally, one may also override episode_finish, which is called at the end of an episode.

Property Description
brain The simulators Brain object.
name The simulators name.
objective_name The name of the current objective for an episode.
episode_reward Cumulative reward for this episode so far.
episode_count Number of completed episodes since sim launch.
episode_rate Episodes per second.
iteration_count Number of iterations for the current episode.
iteration_rate Iterations per second.

Simulator(brain, name)

Example Inkling:

simulator my_simulator(Config)
    action (Action)
    state (State)
end

Example code:

class MySimulator : public Simulator {
 public:
    explicit BasicSimulator(std::shared_ptr<Brain> brain, string name)
        : Simulator(move(brain), move(name)) {
            // your simulator init code goes here.
        }

    void episode_start(const bonsai::InklingMessage& params,
        bonsai::InklingMessage& initial_state) override {
            // your simulation episode reset/init code.
        }

    void simulate(const bonsai::InklingMessage& action,
        bonsai::InklingMessage& state,
        float& reward,
        bool& terminal) override {
            // your simulation stepping code.
        }

    void episode_finish() override {
            // your post-episode code.
        }
};

...

auto config = make_shared<bonsai::Config>(argc, argv);
auto brain = make_shared<bonsai::Brain>(config);
MySimulator sim(brain, "my_simulator");

...

Serves as a base class for running simulations. You should create a subclass of Simulator and implement the episode_start and simulate callbacks.

Argument Description
brain A Brain object for the BRAIN you wish to train against.
name The name of simulator as specified in the Inkling for the BRAIN.

Brain brain()

std::cout << sim.brain() << endl;

Returns the BRAIN being used for this simulation.

string name()

std::cout << "Starting " << sim.name() << endl;

Returns the simulator name that was passed in when constructed.

bool predict()

void MySimulator::simulate(const bonsai::InklingMessage& action,
                    bonsai::InklingMessage& state, float& reward, bool& terminal) {
    if (predict() == false) {
        // calculate reward...
    }

    ...
}

Returns a value indicating whether the simulation is set up to run in predict mode or training mode.

string objective_name()

void MySimulator::episode_start(const bonsai::InklingMessage& params,
                                bonsai::InklingMessage& initial_state) {
    cout << objective_name() << endl;
    ...
}

Property accessor that returns the name of the current objective from Inkling. The objective may be updated before episode_start is called. When running for prediction and during start up, objective will return an empty std::string.

episode_start(parameters, initial_state)

Example Inkling:

schema Config
    UInt8 start_angle
end

schema State
    Float32 angle,
    Float32 velocity
end

Example code:

void MySimulator::episode_start(const bonsai::InklingMessage& params,
                                bonsai::InklingMessage& initial_state) {
    // training params are only passed in during training
    if (predict() == false) {
        cout << objective_name() << endl;
        angle = params.get_float32("start_angle");
    }

    initial_state.set_float32("velocity", velocity);
    initial_state.set_float32("angle",    angle);
}
Argument Description
parameters InklingMessage of episode initialization parameters as defined in inkling. parameters will be populated if a training session is running.
initial_state Output InklingMessage. The subclasser should populate this message with the initial state of the simulation.

This callback passes in a set of initial parameters and expects an initial state in return for the simulator. Before this callback is called, the property objective_name will be updated to reflect the current objective for this episode.

This call is where a simulation should be reset for the next round.

The default implementation will throw an exception.

simulate(action, state, reward, terminal)

Example Inkling:

schema Action
    Int8{0, 1} delta
end

Example code:

void MySimulator::simulate(const bonsai::InklingMessage& action,
                           bonsai::InklingMessage& state, float& reward, bool& terminal) {
    velocity = velocity - action.get_int8("delta");
    terminal = (velocity <= 0.0);

    // reward is only needed during training.
    if (self.predict() == false) {
        reward = reward_for_objective(objective_name());
    }

    state.set_float32("velocity", velocity);
    state.set_float32("angle",    angle);
}
Argument Description
action Input InklingMessage of action to be taken as defined in inkling.
state Output InklingMessage. Should be populated with the current simulator state.
reward Output reward value as calculated based upon the objective.
terminal Output terminal state. Set to true if the simulator is in a terminal state.

This callback steps the simulation forward by a single step. It passes in the action to be taken, and expects the resulting state, reward for the current objective, and a terminal flag used to signal the end of an episode. Note that an episode may be reset prematurely by the backend during training.

For a multi-lesson curriculum, the objective_name will change from episode to episode. In this case ensure that the simulator is returning the correct reward for the different lessons.

Returning true for the terminal flag signals the start of a new episode.

The default implementation will throw an exception.

bool run()

MySimulator sim(brain);

if (sim.predict())
    std::cout << "Predicting against " << brain.name() << " version " << brain.version() << endl;
else
    std::cout << "Training " << brain.name() << endl;

while( sim.run() ) {
}

Main loop call for driving the simulation. Returns false when the simulation has finished or halted.

The client should call this method in a while loop until it returns false. To run for prediction, brain()->config()->predict() must return true.

episode_finish()

void MySimulator::episode_finish() {
    cout << 'Episode: ' << episode_count() << ' reward:' << episode_reward() << endl;
}

This callback is called at the end of each episode. You can use it to log out statistical information, or perform post episode cleanup.

record_file()

my_sim.record_file() == "/path/to/foobar.json";
my_sim.set_record_file("/path/to/barfoo.json");

my_sim.record_file() == "/path/to/foobar.csv";
my_sim.set_record_file("/path/to/barfoo.csv");

Getter and setter for analytics recording file.

When a new record file is set, the previous file will be closed immediately. Subsequent log lines will be written to the new file.

enable_keys(keys, prefix=None)

This function adds the given keys to the log schema for this writer. If one is provided, the prefix will be prepended to those keys and they will appear as such in the resulting logs. If recording is not enabled, this method has no effect.

You should enable any keys you expect to see in the logs. If you attempt to insert objects containing keys which have not been enabled, those keys will be silently ignored.

int main(int argc, char** argv) {
    auto config = std::make_shared<bonsai::Config>(argc, argv);
    auto brain = std::make_shared<Brain>(config);
    MySim sim(brain);
    sim.enable_keys({"foo", "bar"});
    sim.enable_keys({"baz"}, "qux");
}
Argument Description
keys A list/vector of strings to include as keys in log entries for this simulator.
prefix A std::string used as a subdomain for the given keys. Entries will appear as <prefix>.<key> for each key in keys. Defaults to empty string.

record_append(key, value, prefix=“”)

Adds the given key (prepended by prefix, if provided) and value to the current log entry. If the specified key is not enabled or recording is not enabled, this method has no effect.

The following value types are supported: - int64_t - size_t - double - std::string - bool

Note: The template specializations in the following snippet are included for completeness. If the value parameter is of one of the supported types, the correct specialization will be deduced by the compiler.

int main(int argc, char** argv) {
    auto config = std::make_shared<bonsai::Config>(argc, argv);
    config.set_recording_enabled(true);
    config.set_record_filie("foobar.json");
    auto brain = std::make_shared<Brain>(config);
    MySim sim(brain);
    sim.enable_keys({"foo", "bar", "oof", "rab"});
    sim.enable_keys({"baz"}, "qux");

    while (sim.run()) {
        sim.record_append<int64_t>("foo", -23);
        sim.record_append<size_t>("bar", 123);
        sim.record_append<double>("oof", .023);
        sim.record_append<std::string>("rab", "foobar");
        sim.record_append<bool>("baz", true, "qux");
        sim.record_append<int64_t>("nope", 23);
    }
}
Argument Description
key A std::string used as an index into the current log entry.
value The value to add under <prefix>.<key>. May be any of int64_t, size_t, double, std::string, or bool
prefix String prefix for key. Keys should be enabled and added with the same prefix.

close()

Close the internal websocket.

operator<<(ostream, simulator)

Prints out a representation of Simulator that is useful for debugging.

Note: Used in C++ only. For python, use print(simulator)

Argument Description
ostream A std c++ stream operator.
simulator A bonsai::Simulator to print out.

Logging Class

Runtime logging is accomplished using the BONSAI_LOG macro; BONSAI_LOG may be invoked freely on log domains of your choosing, but the accompanying messages will only be printed if a given domain has been enabled either through command line flags (see bonsai::Config) or through bonsai::logging::set_enabled.

Consumers of bonsai::logging may enable a custom handler for BONSAI_LOG via logging::set_handler. By default, logs print to stderr.

Additionally, use logging::set_use_colors to enable/disable ASCII colors in log output.

using bonsai::logging;

class MySimulator : public Simulator {
...
    void simulate(const bonsai::InklingMessage& action,
                   bonsai::InklingMessage& state,
                   float& reward, bool& terminal) {
        ...
        BONSAI_LOG(foobar, "things (" << reward << ") stuff"); // logged
        BONSAI_LOG(barfoo, "things (" << reward << ") stuff"); // not logged
        ...
    }
...
};
int main() {
    ...
    logging::log().set_enabled(true)
    logging::log().set_enabled("foobar")
    ...
}

BONSAI_LOG(domain, message)

BONSAI_LOG is a preprocessor macro defined in logging.hpp. It encapsulates various calls into the logging singleton and performs the necessary domain checks and I/O procedures without the overhead of an additional function call.

void MySimulator::simulate() {
    ...
    BONSAI_LOG(foobar, "my hovercraft has " << val << "eels");
    ...
}

The log domain should not include quotes. It will be quoted into a string literal at preprocessor time.

Similarly, the message will be interpolated into an expression of the form std::cerr << X << std::endl;. As such, message can be any expression which produces syntactically correct C++ when substituted for X in an expression of that form.

enabled()

Global switch to enable/disable logging.

bonsai::logging::log().enabled() == false;
bonsai::logging::log().set_enabled(true);

Individual switches to enable/disable specific domains.

bonsai::logging::log().enabled("foobar") == false;
bonsai::logging::log().set_enabled("foobar");

enabled_all()

bonsai::logging::log().enabled_all() == true;
bonsai::logging::log().set_enable_all(true);

Switch to enable/disable verbose logging.

use_colors()

bonsai::logging::log().use_colors() == true;
bonsai::logging::log().set_use_colors(false);

Switch to enable/disable ASCII colors in log output. Enabled by default.

set_handler(function<void(stringstream&)>)

bonsai::logging::log().set_handler([](std::stringstream& ss) {
    printf("%s", ss.str().c_str());
});

Set a custom handler for BONSAI_LOG. Instead of streaming directly to std::cerr, each log line will be passed to this custom handler in the form of a std::stringstream.

Event Class

Internally, the Bonsai library uses a state machine to drive activity in user code (i.e. advancing and recording simulator state, resetting a simulator, etc). State transfer is driven primarily by a websocket-based messaging protocol shared between the library and the Bonsai AI platform. For your convenience, the details of this protocol have been hidden behind a pair of API’s, one based on callbacks in bonsai_ai.Simulator and the other event driven.

Filling out the callbacks in bonsai_ai.Simulator and relying on Simulator.run to invoke them at the appropriate time will be sufficient for many use cases. For example, if you have a simulator which can be advanced, reset, and observed in a synchronous manner from Python or C++ code, your application is likely amenable to our callback API. However, if, for example, your simulator is free running and communicates with your application code asynchronously, your application will likely need to employ the event driven API described below.

In the event-driven mode of operation, you application code should implement its own run loop by requesting successive events from the Bonsai library and handling them in a way that is appropriate to your particular simulation or deployment architecture. For example, if your simulator invokes callbacks into your code and is reset in response to some outgoing signal (i.e. not via a method call), you might respond to an EpisodeStartEvent by setting the appropriate flag, returning control to the simulator, and returning the resulting state to the Bonsai platform the next time your callback gets invoked.

enum class Type

auto event = get_next_event();
if (event->type() == Type::Episode_Start) {
    auto es_E = dynamic_pointer_cast<EpisodeStartEvent>(event);
    auto initial_properties = es_E->initial_properties;
    auto initial_state = es_E->initial_state;
    // process initial properties/state
} else if (event->type() == Type::Simulate) {
    auto sim_E = dynamic_pointer_cast<SimulatorEvent>(event);
    auto prediction = sim_E->prediction;
    auto state = sim_E->state;
    auto reward = sim_E->reward;
    auto terminal = sim_E->terminal;
    // process simulator step 
} else if (event->type() == Type::Finished) {
    close();
}

Event is an abstract base class for encapsulating Simulator state. Event types correspond to different procedures in client code as shown in the table below.

Value Description
Episode_Start Reset the simulator and set initial state.
Simulate Advance the simulation with the next prediction and record resulting state.
Finished Simulation complete. BRAIN does not expect further state data.
Unknown No event corresponding to last message exchange.

Type type()

Returns the Type of the Event. Implemented for each specialization of Event.

EpisodeStartEvent Class

Signals a boundary between training episodes. Requires resetting the simulation environment and returning its initial state to the BRAIN.

std::shared_ptr initial_properties

Settings used when resetting the simulation environment.

std::shared_ptr initial_state

Directly manipulate to reflect the state resulting from sim environment reset.

See InklingMessage header for details.

SimulateEvent Class

Signals that the BRAIN is ready to receive the simulator state resulting from the next prediction in the queue.

std::shared_ptr prediction

The prediction (action) intended for the next simulation step.

std::shared_ptr state

Directly manipulate to reflect the state resulting from applying prediction to the simulation environment.

See InklingMessage header for details.

std::shared_ptr reward

Directly manipulate to reflect the reward corresponding to state.

std::shared_ptr terminal

Directly manipulate to reflect whether the current state is terminal.

FinishedEvent Class

Signals that the BRAIN is done training. No more simulation steps are expected.

UnknownEvent Class

Signals that no action is required.