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 Python library is generated from the C++ library and you may see some C++ dominant language used in this reference. Please use the language tabs in the code panel to view this reference in either C++ or Python code examples. These libraries are compatible with Python 3+ on 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.

Brain(config, name)

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

auto config = make_shared<bonsai::Config>(argc, argv);
auto brain = make_shared<bonsai::Brain>(config);
std::cout << brain << std::endl;
config = bonsai_ai.Config(sys.argv)
brain = bonsai_ai.Brain(config)
print(brain)
Argument Description
config Object returned by previously created Bonsai::Config (C++) or bonsai_ai.Config() (Python).
name BRAIN name as specified on the server. If name is empty, the BRAIN name in config is used instead.

update()

brain.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;
print(brain.name)

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

string description()

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

Returns the user-provided description for the BRAIN.

int version()

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

Returns the current version number of the BRAIN.

int latest_version()

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

Returns latest version number of the BRAIN.

Config config()

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

Returns the configuration used to locate this BRAIN.

operator<<(ostream, brain)

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

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

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)

import sys, bonsai_ai
if __name__ == "__main__":
    config = bonsai_ai.Config()
    print(config)
#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)

import sys, bonsai_ai
if __name__ == "__main__":
    config = bonsai_ai.Config(sys.argv)
    print(config)
#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.

Note: In Python, argc is not necessary.

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");
my_config.accesskey == '00000000-1111-2222-3333-000000000001'
my_config.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");
my_config.username == 'alice'
my_config.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");
my_config.url == 'https://api.bons.ai'
my_config.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");
my_config.proxy == 'myproxy:5000'
my_config.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");
my_config.brain == 'scarecrow'
my_config.brain = 'scarecrow'

BRAIN name. Name of the BRAIN on the server.

predict()

my_config.predict() == true;
my_config.set_predict(true);
my_config.predict == True
my_config.brain = 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);
my_config.brain_version == 0
my_config.brain_version = 0

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

operator<<(ostream, config)

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

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

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

...

class MySimulator(bonsai_ai.Simulator):
    def __init__(brain, name):
        super().__init__(brain, name)
        # your sim init code goes here.

    def episode_start(self, parameters=None):
        # your reset/init code goes here.
        return my_state

    def simulate(self, action):
        # your simulation stepping code goes here.
        return (my_state, my_reward, is_terminal)

    def episode_finish(self):
        # your post episode code goes here.
        pass

...

config = bonsai_ai.Config(sys.argv)
brain = bonsai_ai.Brain(config)
sim = MySimulator(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;
print(sim.brain)

Returns the BRAIN being used for this simulation.

string name()

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

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...
    }

    ...
}
def simulate(self, action):
    if self.predict is 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;
    ...
}
def episode_start(self, params):
    print(self.objective_name)
    ...

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);
}
def episode_start(self, params):
    # training params are only passed in during training
    if self.predict == False:
        print(self.objective_name)
        self.angle = params.start_angle

    initial = {
        "angle": self.angle,
        "velocity": self.velocity,
    }
    return initial
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);
}
def simulate(self, action):
    velocity = velocity - action.delta;
    terminal = (velocity <= 0.0)

    # reward is only needed during training
    if self.predict == False:
        reward = reward_for_objective(self.objective_name)

    state = {
        "velocity": self.velocity,
        "angle": self.angle,
    }
    return (state, reward, terminal)
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() ) {
}
sim = MySimulator(brain)

if sim.predict:
    print("Predicting against ", brain.name, " version ", brain.version)
else:
    print("Training ", brain.name)

while sim.run():
    continue

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;
}
def episode_finish(self):
    print('Episode:', self.episode_count,
          'reward:', self.episode_reward)

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

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.

Predictor Class

Example Inkling:

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

Example code:

# As a context manager:
config = bonsai_ai.Config(sys.argv)
brain = bonsai_ai.Brain(config)
predictor = Predictor(brain, "my_simulator")

with predictor:
    action = predictor.get_action(state)

# Without context manager:
config = bonsai_ai.Config(sys.argv)
brain = bonsai_ai.Brain(config)
predictor = Predictor(brain, "my_simulator")

action = predictor.get_action(state)
predictor.close()
# Not yet implemented

This class is used to interface with the server to obtain predictions for a specific BRAIN and is a subclass of Simulator. This class is currently unavailable in libbonsai so only Python implementations are shown.

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

Argument Description
brain The name of the BRAIN to connect to.
name The name of this simulator. Must match simulator in Inkling.

get_action(self, state)

Receives the Inkling action when sent a state.

close(self)

Closes a websocket connection. This is recommended when predictor() is used outside of the context manager.