What is poli?#

poli is a library for creating and calling black box optimization functions, with a special focus on discrete sequence optimization. It stands for Protein Objectives Library, since some of the work done on drug design is done through representing proteins and small molecules as discrete sequences.

We also build poli-baselines on top, allowing you to define black box optimization algorithms for discrete sequences.

These next chapters detail a basic example of how to use poli and poli-baselines. If you want to start coding now, continue to the next chapter!

The rest of this intro details the usual development loops we assume you’ll follow when using poli and poli-baselines:

The usual development loop#

Black-box optimization algorithms inside poli-baselines are treated as solvers, and the discrete objective functions of poli are described as problems.

We propose to you the following process for using poli-baselines’ optimizers, or developing your own:

Identify the objective function#

Start by identify the black-box objective function you want to optimize, and check if it’s already registered in poli, or available in poli’s objective repository.

This can be done by running

from poli import get_problems
print(get_problems())
['aloha', 'dockstring', 'drd3_docking', 'foldx_rfp_lambo', 'foldx_sasa', 'foldx_stability', 'foldx_stability_and_sasa', 'gfp_cbas', 'gfp_select', 'penalized_logp_lambo', 'rasp', 'rdkit_logp', 'rdkit_qed', 'rfp_foldx_stability_and_sasa', 'sa_tdc', 'super_mario_bros', 'white_noise', 'toy_continuous_problem']

The output is a list of problems you may be able to run.

Note

Some objective functions have more requirements, like installing external dependencies. Check the page on all objective functions and click on the objective function you are interested in to get a detailed set of instructions on how to install and run it.

In what follows, we will use the white_noise objective function. You could drop-in another function if desired.

# One way to create a white noise problem/black box
from poli import objective_factory

problem = objective_factory.create(name="white_noise")
f, x0 = problem.black_box, problem.x0

# Another way
from poli.objective_repository import WhiteNoiseBlackBox

f = WhiteNoiseBlackBox()
poli 🧪: Creating the objective white_noise from the repository.

At this point, you can call f on arrays of shape [b, L]. In the specific case of white_noise, L can be any positive integer.

Using a solver, or creating your own#

poli-baselines also comes with black-box optimizers out-of-the-box. You can find them inside the library.

For example, let’s use the RandomMutation solver, which takes the initial x0 and randomly mutates it according to the alphabet provided in problem_info.

from poli_baselines.solvers.simple.random_mutation import RandomMutation

y0 = f(x0)

solver = RandomMutation(
    black_box=f,
    x0=x0,
    y0=y0,
)

print(f"x0: {x0}")
print(f"y0: {y0}")
/Users/sjt972/anaconda3/envs/poli-docs2/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
x0: [['1' '2' '3']]
y0: [[1.08390547]]

Solvers implement a next_candidate() method, based on their history:

solver.next_candidate()
array([['4', '2', '3']], dtype='<U1')

RandomMutation simply selects one token at random from the alphabet:

solver.alphabet  # It's the same as f.info.alphabet
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

If you are interested in building your own solver, check out the chapter detailing how RandomMutation is implemented.

Optimizing#

Once you have a black box objective function f and a solver on top, the optimization is quite easy:

solver.solve(max_iter=100)
print(solver.get_best_solution())
[['1' '5' '3']]

Of course, this example is trivial. We dive deeper in the next chapters.

Conclusion#

This chapter discusses the usual development loop using poli and poli-baselines:

  1. Start by identifying/building your objective function,

  2. continue by creating/using a solver in poli_baselines, and

  3. use the solve method to run a number of iterations from the solver.

The next three chapters talk about another trivial example, diving deeper in the process of defining your own objective functions and solvers. You can continue there, or by checking the currently implemented repository of objective functions inside poli.