Batch Execution#

Typically we might want to execute batches of simulation across random seeds and simulation parameter samples, verbs.sim.batch_runner.batch_run() implements functionality to generate simulation samples in parallel.

A typical use-case is generating an initial environment snapshot from a remote fork (which is generally quite slow) and then quickly generate a large batch of samples from the snapshot.

The simulation environments for the samples can be initialised from either a snapshot (using the verbs.envs.ForkEnv.export_snapshot() method) or a cache (generated using the verbs.envs.ForkEnv.export_cache() method). In this is example we will create an initial snapshot after manually deploying a contract and minting some tokens:

env = verbs.EmptyEnvRandom(1234)

admin_address = verbs.utils.int_to_address(99999999)
env.create_account(admin_address, int(1e19))

erc20_abi = verbs.abi.get_abi("ERC20", ERC20_ABI)
erc20_address = erc20_abi.constructor.deploy(
    env, admin_address, ERC20_BYTECODE, [int(1e19)]
)

snapshot = env.export_snapshot()

Batch execution requires a simulation execution function with the signature

def sim_func(
    env, seed, n_steps, **params, **sim_kwargs
) -> typing.Any:
    ...

In this example we will re-use the previous example

def sim_func(
    env,
    seed,
    n_steps,
    *,
    activation_rate,
    erc20_address,
    erc20_abi,
    admin_address,
):
    agents = [
        Agent(i + 100, erc20_address, erc20_abi, N_AGENTS, activation_rate)
        for i in range(N_AGENTS)
    ]

    erc20_abi.transfer.execute(
        env,
        admin_address,
        erc20_address,
        [agents[0].address, int(1e19)],
    )

    runner = verbs.sim.Sim(seed, env, agents)
    results = runner.run(n_steps)
    return results

which initialises a set of agents, transfers tokens to one of the agents, then runs the simulation.

The sampling can then be run using the verbs.batch_runner.batch_run() function, providing the sim_func function and a set of parameters to sample over

batch_results = batch_run(
    sim_func,
    n_steps=100,
    n_samples=10,
    parameters_samples=[
        dict(activation_rate=0.1), dict(activation_rate=0.2)
    ],
    snapshot=snapshot,
    erc20_address=erc20_address,
    erc20_abi=erc20_abi,
    admin_address=admin_address,
 )

The batch-runner will generate sample and random seed combinations, and execute simulation across these combinations in parallel. In this example it will generate 10 Monte-Carlo samples for each set of parameters (20 samples, 2 parameter sets x 10 random seeds) each run for 100 steps.

For convenience the results are returned grouped by the parameters used to generate them, in this case they will have the structure

[
    {
        "params": {"activation_rate": 0.1},
        "samples": [
            # List of Monte-Carlo sample results
            ...
        ]
    },
    {
        "params": {"activation_rate": 0.2},
        "samples": [
            # List of Monte-Carlo sample results
            ...
        ]
    }
]