Configuration

General overview of the possible individual configuration of a simulation using the configuration attribute of the bptk constructor
Keywords

configuration, bptk, bptk-py, python, business simulation

Configuration

This document explains the configuration settings of a bptk object.

The code on this page is shown rather than run: it configures logging, file monitors and where scenarios are read from, none of which has an effect you could see on a documentation page — and some of which needs a token or a directory that only exists on your own machine. The one section that is live is the last one, on graphical settings.

General

The bptk constructor accepts two arguments:

  • loglevel — adjusts the global logging level, so it also applies to other bptk instances in the same process.
  • configuration — a dictionary, and the subject of the rest of this page.

Configure Logfire

Logging to Logfire is enabled globally through the configuration argument. It needs the observability extra:

pip install "BPTK-Py[observability]"

Register with Logfire, obtain a write token, and keep it out of your code — an .env file beside your notebook is the usual place. Reading it needs python-dotenv, which BPTK does not depend on; install it alongside:

pip install python-dotenv
# .env
LOGFIRE_TOKEN=pylf_v1_eu_your_write_token_here

Then read it and hand it to the constructor:

import os
from dotenv import load_dotenv
from logfire import ConsoleOptions
from BPTK_Py import bptk

load_dotenv()

bptk_instance = bptk(
    loglevel="INFO",
    configuration={
        "logfire_config": {
            "environment": "development",
            "token": os.getenv("LOGFIRE_TOKEN"),
            "console": ConsoleOptions(show_project_link=False),
        }
    },
)

Once Logfire is configured, every message written to bptk_py.log (the default logfile name) is also sent to Logfire. Inside "logfire_config" you can pass both the essentials — "environment" and "token" — and any of the optional settings that control Logfire’s behaviour and its appearance in the console. The full list is in the Logfire configuration reference.

Logfire can also be configured directly, without going through a bptk instance:

import BPTK_Py.logger.logger as logmod
from logfire import ConsoleOptions

logmod.configure_logfire(
    token=os.getenv("LOGFIRE_TOKEN"),
    console=ConsoleOptions(show_project_link=False),
)

Without the observability extra installed, this raises ImportError naming the extra.

Configure Additional Logging Settings

Two further keys control where log messages go. Both are applied globally.

  • "log_modes" — list of strings, any of "print" and "logfile". Default: ["logfile"].
  • "log_file" — string, the name of the logfile. Default: "bptk_py.log".
bptk_instance = bptk(
    loglevel="WARN",
    configuration={
        "log_modes": ["print", "logfile"],
        "log_file": "test_bptk.log",
    },
)

With this setting, log messages are written both to test_bptk.log and to the console.

loglevel decides which messages are logged at all: "ERROR" lets only errors through, "WARN" (the default) errors and warnings, and any other value, such as "INFO", everything.

Errors are always printed to the console as well, whatever "log_modes" says - so an invalid argument to a builtin, which logs an error, is seen without any setting.

Configure Scenario and Model Monitor

For each bptk instance you can decide whether changes to scenario files and model files are detected and applied automatically.

  • "set_scenario_monitor" — boolean. When True, a FileMonitor thread runs for each scenario JSON file and reloads the scenarios in it when the file changes on disk. Default: True.
  • "set_model_monitor" — boolean. When True, a ModelMonitor thread runs for the associated model file and updates every scenario that depends on it when the file changes. Default: True.

To switch both off:

bptk_instance = bptk(
    loglevel="WARN",
    configuration={
        "set_scenario_monitor": False,
        "set_model_monitor": False,
    },
)

Neither monitor runs in a browser: they need threads that never return, which the browser platform does not provide.

Choose the execution engine

  • "default_backend" — "python" or "rust". The engine every call of this instance uses unless it is given a backend of its own: run_scenarios(), plot_scenarios(), begin_session(). Default: "python".
bptk_instance = bptk(configuration={"default_backend": "rust"})

It is read when the instance is created. See Execution Backends.

Configure the path to scenario storage

A bptk instance finds its scenarios through the "scenario_storage" key, which defaults to "scenarios/" — a folder named scenarios in the working directory. The path can be relative to the working directory or absolute.

The folders beside this page are laid out like this, and you can open the files:

concepts/
├── configuration/
│   └── subfolder1/
│       └── scenarios/
│           └── scenario2.json
└── folder2/
    └── scenarios/
        └── scenario3.json

To load scenario2.json, which sits one level below this page:

bptk_instance = bptk(
    loglevel="INFO",
    configuration={"scenario_storage": "subfolder1/scenarios/"},
)

To load scenario3.json, which sits in a sibling of this page’s folder, use a relative path pointing one level up:

bptk_instance = bptk(
    loglevel="INFO",
    configuration={"scenario_storage": "../folder2/scenarios/"},
)

An absolute path finds the same files from any working directory:

bptk_instance = bptk(
    configuration={"scenario_storage": "/home/me/my_project/scenarios/"},
)

The parent of the "scenario_storage" folder is the project directory, and it is added to sys.path. The "model" inside a scenario JSON is named relative to it, in either of two notations: a path to a module, "simulation_models/my_model", or a dotted class, "src.my_model.MyModel". Both work whether the storage path is relative or absolute.

The model does not have to sit below the scenarios. Here the two are siblings, and the script beside them can be started from anywhere:

my_project/
├── Model/
│   └── growth.py        # class Growth(Model)
├── Scenario/
│   └── growth.json      # "model": "Model.growth.Growth"
└── run.py

run.py builds the storage path from its own location, so it is absolute:

from pathlib import Path
from BPTK_Py import bptk

project = Path(__file__).resolve().parent
bptk_instance = bptk(configuration={"scenario_storage": str(project / "Scenario")})

my_project is the parent of Scenario, so it is the project directory, and "Model.growth.Growth" names my_project/Model/growth.py. With the path notation the same file would be "Model/growth", which expects the class to be called simulation_model.

Configure graphic settings

Plotting reads its defaults from the same configuration dictionary, so you can set the look of every plot an instance produces in one place instead of per call. This section is live — the model below is built in the page, so both plots really run.

With no graphic settings at all, an instance plots like this:

Who decides how a plot looks

Passing configuration to the constructor sets the look of that instance: plot_scenarios and plot_lookup on it draw the way you asked, and nothing else in your session changes.

BPTK_Py.bptk(
    configuration={
        "kind": "bar",              # bars instead of lines
        "stacked": False,           # side by side rather than on top of each other
        "colors": ["Red", "Blue", "Green"],
        "alpha": 0.98,              # almost opaque
        "matplotlib_rc_settings": {
            "xtick.labelsize": 10,
            "ytick.labelsize": 12,
            "legend.fontsize": 14,
        },
    }
)

Two plot methods have no BPTK_Py.bptk() in reach and cannot read an instance’s look: Element.plot() and the agent data collector’s plot_agent_stats(). Those follow BPTK_Py.plotting_config, which is also what every instance starts from:

BPTK_Py.plotting_config.update({"kind": "bar", "colors": ["Red", "Blue"]})

Set that before you build your bptk() objects and they all draw that way, the two methods above included. An instance that already exists keeps the look it was built with, so a cell you ran earlier does not change under you. BPTK_Py.plotting_config.reset() gets back to the defaults.

Charts you draw yourself are not affected. The settings are applied around our own drawing calls, never written into matplotlib’s global rcParams, so a figure you build with plt.subplots() keeps matplotlib’s own defaults.

The default title size is chosen for the default figure width of 20. A different figsize scales the title with it, so a narrow plot does not cut its title off; a title size you set yourself with "axes.titlesize" is used as it is.

Styling a single plot

Where one chart should differ, hand the settings to that call instead. They are laid over the configuration for this one draw and leave it as it is, so the plot above keeps its own look. Change a value and press play: